Re: find_all_inheritors() with NoLock fails on concurrently dropped sub-partitions

From: Zsolt Parragi <zsolt(dot)parragi(at)percona(dot)com>
To: surya poondla <suryapoondla4(at)gmail(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>
Subject: Re: find_all_inheritors() with NoLock fails on concurrently dropped sub-partitions
Date: 2026-10-14 08:59:24
Message-ID: CAN4CZFMp1RDUsBUcjQpaf6TcDbmmueTW90haZ_47vs3sk6rUKQ@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

> For the main fix, I'd make it narrower than v1, leave has_subclass() and
> find_inheritance_children() as they are, and only let
> find_all_inheritors() skip a missing relation when called with NoLock (and
> never for the starting relation, which the caller must lock).
> A static helper with a missing_ok flag would do it without changing any
> exported function.

Thanks for the review, that makes sense. My logic for changing
find_inheritance_children_extended was that this error outside NoLock
is only a sanity check and shouldn't happen, but you are right that we
could keep the more robust code.

v2 attached does exactly this: find_inheritance_children_extended() is
now a wrapper around a static find_inheritance_children_internal()
with a missing_ok flag, and only find_all_inheritors() sets it with
NoLock and not for the starting relation.

> Without NOWAIT it can just pass the lock mode to find_all_inheritors().
> With NOWAIT it would need to go back to locking one level at a time.
> That could be a separate patch.

Agreed, I left LOCK TABLE alone in this patch.

> 1. This isn't limited to sub-partitioned tables. Dropping a partition
> only locks its direct parent, so VACUUM's lock on the
> root protects the first level and nothing below it. Plain leaves at the
> second level are enough.

You are right, my example was more complex than necessary, I updated
the commit message.
(But also see the next point)

> 2. If the injection point were in the find_all_inheritors() loop instead of
> after the relhassubclass check, the test wouldn't need
> the extra q/q1 tables. root -> p1 -> {a, b} would do, and it would cover
> the leaf case too.

I moved the injection point to the top of the find_all_inheritors()
loop, but I still needed one extra table to make it fail reliably.
To reliably trigger it we also need a pg_inherits scan after the drop,
since that has no syscache and takes a new catalog snapshot.

So the test now uses root -> p1 -> {a -> a1, b}, waits before a is
expanded, and drops the leaf b.

> 3. The test comment "q's pg_inherits scan doesn't see p2 anymore" is a bit
> misleading. q's scan never looks at p2; q just gets
> expanded first because it has the smaller OID.

Right, that comment was wrong, the new one explains what I wrote above.

Attachment Content-Type Size
v2-0001-Fix-find_all_inheritors-with-NoLock-against-concu.patch application/octet-stream 8.9 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message David Rowley 2026-10-14 09:11:09 Re: Memoize shows only the last execution's stats for parallel workers
Previous Message Bingshuai Li 2026-10-14 08:37:00 RE: Bug in logical decoding with DDL and subtransactions