On 28/09/2026 18:24, Andres Freund wrote:
> Hi,
>
> On 2026-09-28 17:07:52 +0200, Kevin Rocker wrote:
>>> The reason this is more than a cancellation-latency issue on v19 is the
>>> buffer content-lock rewrite in fcb9c977aa5 tracks only one lock per
>>> buffer per backend (the single data.lockmode field). A second SHARE
>>> acquire on an already-share-locked buffer is now:
>>>
>>> - a hard Assert/crash in cassert builds (what unicorn shows), and
>>> - in a non-assert build, an asymmetric leak: BufferLockAttempt() adds
>>> a second BM_LOCK_VAL_SHARED to the shared state, data.lockmode
>>> records only one, and release subtracts one -- so that pg_class
>>> buffer is left permanently one shared-locker too high and can never
>>> again be locked exclusive. Any later exclusive waiter (VACUUM) on
>>> that buffer blocks for the life of the cluster.
>>>
>>> Pre-v19 the double SHARE was harmless (the held-lwlocks array could
>>> represent it), which is presumably why the call site survived so long.
>
> I think this was completely broken before 19 too. Acquiring a lock while
> holding the same lock just happened to be undiagnosed. Note that if you ever
> did this with an exclusive lock being involved, you'd just have ended up with
> an uninterruptible endless wait.
>
> It surely was never safe to call ProcessConfigFile(), or sane to sleep, while
> holding an lwlock. And calling vacuum_delay_point() with interrupts held, made
> it not actually properly work, due to not doing the CFI().
+1
I just arrived at this thread from this other thread discussing the same
issue:
https://www.postgresql.org/message-id/19628-c2b17d358181a1ea%40postgresql.org.
I committed and backported the fix to all stable branches. I included a
quick exit in vacuum_delay_point(), if !INTERRUPTS_CAN_BE_PROCESSED().
- Heikki