Re: Streaming decoding fails with "unexpected table_index_fetch_tuple call during logical decoding" when a relation has a TOASTed conbin (follow-up to BUG #18641)

From: Jiří Kavalík <jiri(dot)kavalik(at)comgate(dot)cz>
To: "Hayato Kuroda (Fujitsu)" <kuroda(dot)hayato(at)fujitsu(dot)com>
Cc: pgsql-bugs <pgsql-bugs(at)lists(dot)postgresql(dot)org>
Subject: Re: Streaming decoding fails with "unexpected table_index_fetch_tuple call during logical decoding" when a relation has a TOASTed conbin (follow-up to BUG #18641)
Date: 2026-10-02 08:29:03
Message-ID: CAF7a2M9FmfsvJkYTZBvjKyuD5j46_PhFWkDq-JY9mHaaknMF2w@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

Hi Kuroda-san,

Thank you for the quick analysis and the patch.

> Can you see it's same as your expectation?

Yes. I tested v1 on REL_18_STABLE (37bbf5bba0, 18.6 plus later commits),
with
two builds from the same tree differing only by the patch, both configured
with --enable-debug --enable-cassert.

In each case below, a fresh backend peeks at a slot while a transaction that
first inserts one row into the test table, then 10000 rows into a filler
table, is still open (logical_decoding_work_mem = 64kB, CHECKPOINT before
the
peek):

1. TOASTed conbin first, three more constraints after it:
- test_decoding: unpatched ERROR, patched OK
- pgoutput, table published: unpatched ERROR, patched OK
- pgoutput, table NOT published: unpatched ERROR, patched OK

2. TOASTed conbin first, only the NOT NULL and PK constraints after it:
- test_decoding: unpatched ERROR, patched OK
- pgoutput, table published: unpatched ERROR, patched OK
- pgoutput, table NOT published: unpatched ERROR, patched OK

3. TOASTed conbin last: OK on both builds, both plugins.

4. No TOASTed conbin: OK on both builds, both plugins.

"OK" means the whole transaction was decoded (10098 rows from test_decoding,
10100 from pgoutput). There were no assertion failures in either server log.

Your new stream.sql test fails on the unpatched build with the same ERROR
and
passes with the patch. With the patch, contrib/test_decoding "make check"
(19 regression + 13 isolation tests) and the core "make check" (231 tests)
pass.

The "table NOT published" rows may be worth noting: the relation that hit
the
error in our production case is not in the subscribed publication, it is
only
modified by the streamed transaction. The relcache entry is still built, so
the bug is reachable for any table the transaction touches.

One question while reading the patch, not a problem I could trigger: the
depth
is incremented in systable_beginscan* and decremented in systable_endscan*
only
if CheckXidAlive is valid, and that is evaluated separately at each end. If
CheckXidAlive changed while a scan was open, the counter would be off by
one.
I could not find a path where that happens. Error paths look fine, since
AbortTransaction/AbortSubTransaction call ResetLogicalStreamingState(). If a
SysScanDesc field is acceptable despite the header concern, remembering in
the
scan whether it was counted would make the pairing explicit.

Regards,
Jiří Kavalík

pá 2. 10. 2026 v 9:33 odesílatel Hayato Kuroda (Fujitsu) <
kuroda(dot)hayato(at)fujitsu(dot)com> napsal:

> Hi,
>
> This is the reply for [1]. My mailer could not receive the original post
> due to
> the company's policy, so I will put as the normal post. Sorry for
> inconvenience.
>
> I confirmed this could happen on PG18, PG17 and PG14. Not tested, but
> expecting
> for PG15 and 166 as well. This could not happen on PG19/HEAD because the
> elog(ERROR) was removed by 87f7b824f20, but possible for all branches.
>
> I think your analysis is correct. bsysscan tries to indicate that whether
> we're
> scanning a system table, which was turned on at systable_beginscan* and
> turned
> off at systable_endscan*. But if the systable scan is nested (i.e.,
> pg_constraint.conbin),
> the flag can be wrong reset. In PG18- the state is checked for every
> getnextslot,
> which raised the ERROR. In PG19+ the check is unified at the beginning
> thus the
> ERROR does not happen, but I guess the flag can be still wrong.
>
> One idea is to track the depth of scans. Attached patch is for PG18, and I
> tried not
> to modify the header as much as possible. It also had a test code based on
> your
> reproducer. Can you see it's same as your expectation?
>
> [1]:
> https://www.postgresql.org/message-id/CAF7a2M-OF%2BTjYUBvkSV2oyZNYuPm%3DEqTApcMDOC%2BaevBFPuWrg%40mail.gmail.com
>
> Best regards,
> Hayato Kuroda
> FUJITSU LIMITED
>
>

--
S pozdravem
Jiří Kavalík
jiri(dot)kavalik(at)comgate(dot)cz
Comgate a.s.
Gočárova třída 1754/48b, 500 02 Hradec Králové
www.comgate.cz

In response to

Browse pgsql-bugs by date

  From Date Subject
Previous Message Vladimir Savin 2026-10-02 08:12:31 Re: PG18: use-after-free in exec partition pruning after an EPQ recheck in LockRows