Re: [PATCH]Fix pg_xact corruption from subtransaction abort after subcommit

From: Andrey Rachitskiy <pl0h0yp1(at)gmail(dot)com>
To: Andrey Borodin <x4mmm(at)yandex-team(dot)ru>
Cc: Bryan Green <dbryan(dot)green(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: [PATCH]Fix pg_xact corruption from subtransaction abort after subcommit
Date: 2026-08-09 22:38:46
Message-ID: CAB8bMivHtNJXKJBhZwsGXOCQ6wxo9cxTa7DxkwsHsG0wwfnv_Q@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi, Andrey!

On whether a catchable ERROR can happen after AtSubCommit_childXids()
without an extension callback: yes. Allocations inside stock
AtSubCommit_Notify() are enough to raise one. That reaches Bryan's
steps (A) and (B) below. It does not by itself give a neat clog TRAP on
the later parent COMMIT. Under this OOM setup that COMMIT usually dies
in SIGSEGV first. The childXids / pg_xact split is already present
before COMMIT, which is the corruption window Bryan described.

Bryan's sequence is:

(A) AtSubCommit_childXids() publishes the subxid into parent->childXids.
(B) A later catchable ERROR aborts the subxact and marks that XID aborted
in pg_xact, without removing it from parent->childXids.
(C) Parent COMMIT then calls TransactionIdSetTreeStatus() / CommitTree
over that mixed list (assert TRAP, or silent clog corruption).

(A)+(B) are visible under gdb with no extension. For a clean (C) with
the clog assert, an extension COMMIT_SUB callback that raises ERROR
still works as Bryan sketched.

Setup in short: parent and subxact both queue NOTIFYs, then force
memory pressure so the pending-notify merge in AtSubCommit_Notify()
hits a catchable out-of-memory ERROR; PL/pgSQL EXCEPTION WHEN OTHERS
swallows it.

Observed under gdb:

1) (A) Subxid published into the parent list, still before Notify merge:

>>> AtSubCommit_childXids ENTER xid=696 parent->nChildXids=0
>>> after AtSubCommit_childXids: parent->nChildXids=1 childXids[0]=696
>>> AtSubCommit_Notify ENTER parent->nChildXids=1 childXids[0]=696

2) Catchable OOM later in the same CommitSubTransaction():

#0 MemoryContextAllocationFailure (...) at mcxt.c:1203
#6 AddEventToPendingNotifies (...) at async.c:3228
#7 AtSubCommit_Notify () at async.c:2498
#8 CommitSubTransaction () at xact.c:5207
#9 ReleaseCurrentSubTransaction () at xact.c:4837
#10 exec_stmt_block (...) at pl_exec.c:1859
...

3) (B) Abort marks the same XID aborted in pg_xact while it remains in
parent->childXids (AbortSubTransaction -> RecordTransactionAbort ->
TransactionIdAbortTree; AtSubAbort_childXids only frees *this* subxact's
own child list, not the parent's entry):

>>> TransactionIdAbortTree xid=696 nchildren=0

==== BUG STATE after CleanupSubTransaction ====
parent->nChildXids = 1
parent->childXids[0] = 696 DidAbort=1 DidCommit=0
==== end BUG STATE ====

вс, 9 авг. 2026 г. в 22:57, Andrey Borodin <x4mmm(at)yandex-team(dot)ru>:

> Hi Bryan,
>
> Thank you for finding this.
>
> > On 9 Aug 2026, at 08:39, Bryan Green <dbryan(dot)green(at)gmail(dot)com> wrote:
> >
> > path can corrupt pg_xact
>
> That's a very interesting corruption case. We observed something very much
> like
> this with "failed to find parent tuple for heap-only tuple".
>
> But so far I can't build a test with an error between
>
> AtSubCommit_childXids();
> and
> PopTransaction();
>
> I meant, yeah, extensions can do this, but that's a weak argument. Maybe
> some
> allocations inside AtSubCommit_Notify() can cause catachable error... IDK.
>
> This actually looks like a corruption that bite us. But how an error can
> happen
> in this path? If it's an extension, which extension is thorwin it? In-tree
> extensions seems to avoid erroring out in callbacks. Maybe something in
> AtEOXact_GUC()?
>
>
> Best regards, Andrey Borodin.
>
>

--
Regards,
Rachitskiy Andrey

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Tom Lane 2026-08-09 23:53:57 Re: [PATCH] Fix vacuum_delay_point happening inside lock
Previous Message Kevin Rocker 2026-08-09 22:36:37 Re: [PATCH] Fix vacuum_delay_point happening inside lock