agora inbox for pgsql-committers@postgresql.orghelp / color / mirror / Atom feed
pgsql: Fix logical decoding of empty prepared transactions. 7+ messages / 1 participants [nested] [flat]
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ master Details ------- https://git.postgresql.org/pg/commitdiff/acaa100f9a0c8dbd20ef48b3ce8b94bb773d6acc Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 34 +++++++++++++++++--- src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_19_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/2289e65e15ee53cbba2d96543553c467c9ccbc4e Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 34 +++++++++++++++++--- src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_18_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/b563fc6bd926fd9dca86d0c1a16fbf2e3e63dee9 Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 34 +++++++++++++++++--- src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_17_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/8c4519c71aec0f2b78d30b6586750ce1ae7eac84 Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 18 +++++++++++ src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 96 insertions(+) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_16_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/7446183463b45f0c9ec45ad1a9ad4cc08190218c Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 18 +++++++++++ src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 96 insertions(+) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:34 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:34 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_15_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/0dbfb8520f609f93f4fd05810be6f5bec601911f Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++ src/backend/replication/logical/reorderbuffer.c | 18 +++++++++++ src/test/subscription/t/021_twophase.pl | 41 +++++++++++++++++++++++++ 4 files changed, 96 insertions(+) ^ permalink raw reply [nested|flat] 7+ messages in thread
* pgsql: Fix logical decoding of empty prepared transactions. @ 2026-07-28 19:35 Masahiko Sawada <msawada@postgresql.org> 0 siblings, 0 replies; 7+ messages in thread From: Masahiko Sawada @ 2026-07-28 19:35 UTC (permalink / raw) To: pgsql-committers@lists.postgresql.org Fix logical decoding of empty prepared transactions. A two-phase transaction that is assigned an XID but produces no change to be decoded -- for example, one that only acquires row locks via SELECT ... FOR SHARE -- has no base snapshot in the reorder buffer. ReorderBufferReplay() already skips such a transaction at PREPARE time and never invokes the begin_prepare/change/prepare callbacks for it, but ReorderBufferFinishPrepared() still called the commit_prepared (or rollback_prepared) callback. As a result a spurious COMMIT/ROLLBACK PREPARED was sent to the output plugin with no preceding PREPARE. For the built-in subscriber this breaks replication (the apply worker fails to find the prepared transaction), and test_decoding could even crash. Fix this by detecting an empty transaction (base_snapshot == NULL) in ReorderBufferFinishPrepared() and cleaning it up without invoking the commit/rollback prepared callbacks, mirroring the existing empty transaction handling in ReorderBufferReplay(). On v18 and newer versions, commit 072ee847ad4 changed ReorderBufferPrepare() to send the prepare whenever it had not already been sent, which also fires for empty transactions and emits a spurious PREPARE. On those branches ReorderBufferPrepare() is therefore additionally guarded with base_snapshot != NULL. This guard and the Assert(!rbtxn_sent_prepare()) added in ReorderBufferFinishPrepared(), are not necessary on v17 and older versions: there ReorderBufferPrepare() only sends a prepare for concurrently-aborted transactions (which never applies to an empty transaction) and the RBTXN_SENT_PREPARE flag does not exist. Back-patch to v14, where decoding of two-phase transactions was introduced. Bug: #19556 Reported-by: Alexander Kozhemyakin <a.kozhemyakin@postgrespro.ru> Reviewed-by: Amit Kapila <amit.kapila16@gmail.com> Discussion: https://postgr.es/m/19556-daa6d7ea65054d48@postgresql.org Backpatch-through: 14 Branch ------ REL_14_STABLE Details ------- https://git.postgresql.org/pg/commitdiff/c2d34db0a5eced16cf8d7e1004674b20b8fa383d Modified Files -------------- contrib/test_decoding/expected/twophase.out | 25 +++++++++++++++++++++++++ contrib/test_decoding/sql/twophase.sql | 12 ++++++++++++ src/backend/replication/logical/reorderbuffer.c | 18 ++++++++++++++++++ 3 files changed, 55 insertions(+) ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2026-07-28 19:35 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:34 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org> 2026-07-28 19:35 pgsql: Fix logical decoding of empty prepared transactions. Masahiko Sawada <msawada@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox