From 0ea74fd41e404bc35a3d03cc46dc5c1f0d7e0d69 Mon Sep 17 00:00:00 2001 From: Bharath Rupireddy Date: Wed, 2 Sep 2026 21:59:36 +0000 Subject: [PATCH v3 1/3] Fix LockHasWaiters() crash for fast-path locks. LockHasWaiters() assumes that the LOCALLOCK's lock and proclock pointers are populated, but this is not the case for locks acquired via the fast-path optimization. Weak relation locks (those below ShareUpdateExclusiveLock, including AccessShareLock) are not stored in the shared lock hash table, leaving the LOCALLOCK entry with lock = NULL and proclock = NULL. If LockHasWaiters() is called for such a lock, it dereferences those NULL pointers when reading proclock->holdMask and lock->waitMask, causing a segfault. Having LockHasWaiters() transfer the lock into the main lock table with FastPathGetRelationLockEntry() would avoid the crash, but that gives a read-only check the side effect of adding entries to the main lock table. It is also unnecessary, because if nobody else has moved our lock into the main lock table, it has no waiters. Fix by looking up the main lock table when the LOCALLOCK pointers are NULL. If no entry is found, the lock is still held via the fast path and cannot have any waiters, so we return false without moving it. If an entry is found, some backend already transferred the lock, and we re-find the lock and proclock as LockRelease() does. Reported-by: Satyanarayana Narlapuram Author: Bharath Rupireddy Co-authored-by: Satyanarayana Narlapuram Reviewed-by: Robert Haas Reviewed-by: Michael Paquier Discussion: https://www.postgresql.org/message-id/flat/CAHg%2BQDe_%3DZahnRx37bzrqYenKn_S5YDQ00fTfwe-ZUmjqO%3DqLg%40mail.gmail.com --- src/backend/storage/lmgr/lock.c | 40 ++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c index cf5c98c8176..838a5f251b5 100644 --- a/src/backend/storage/lmgr/lock.c +++ b/src/backend/storage/lmgr/lock.c @@ -743,11 +743,45 @@ LockHasWaiters(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) LWLockAcquire(partitionLock, LW_SHARED); /* - * We don't need to re-find the lock or proclock, since we kept their - * addresses in the locallock table, and they couldn't have been removed - * while we were holding a lock on them. + * Normally we can rely on the lock and proclock addresses kept in the + * locallock table. But if the lock was acquired via the fast path, those + * pointers are NULL, because the lock was never entered in the shared + * lock table. A fast-path lock is a weak relation lock, and it can only + * gain a waiter if some backend requests a conflicting (strong) lock, and + * that request first moves all matching fast-path locks into the shared + * table (see FastPathTransferRelationLocks()). So if we still find no + * shared lock entry, the lock cannot have any waiters, and we return + * false without moving it. If another backend did move it, look up the + * lock and proclock here, the same way LockRelease() does. */ lock = locallock->lock; + if (!lock) + { + PROCLOCKTAG proclocktag; + + Assert(EligibleForRelationFastPath(locktag, lockmode)); + lock = (LOCK *) hash_search_with_hash_value(LockMethodLockHash, + locktag, + locallock->hashcode, + HASH_FIND, + NULL); + if (!lock) + { + /* Still fast-path only, so nobody could be waiting on it. */ + LWLockRelease(partitionLock); + return false; + } + locallock->lock = lock; + + proclocktag.myLock = lock; + proclocktag.myProc = MyProc; + locallock->proclock = (PROCLOCK *) hash_search(LockMethodProcLockHash, + &proclocktag, + HASH_FIND, + NULL); + if (!locallock->proclock) + elog(ERROR, "failed to re-find shared proclock object"); + } LOCK_PRINT("LockHasWaiters: found", lock, lockmode); proclock = locallock->proclock; PROCLOCK_PRINT("LockHasWaiters: found", proclock); -- 2.47.3