Temporary slot leak when creation fails in a subtransaction

Started by Bharath Rupireddy27 days ago7 messageshackers
Beta feature

Hackorum builds and tests every patch posted to the lists, not only commitfest submissions. This is Hackorum's own CI rather than the PostgreSQL project's, and it is still under testing - please report anything that looks wrong.

never appliedCI history
Jump to latest
#1Bharath Rupireddy
bharath.rupireddyforpostgres@gmail.com

Hi,

Moving the discussion from [1]/messages/by-id/CAHg+QDeuf9tCq3ce=kgFMJP0m=PZC+wi6B=yS+7V0vNXjLS31w@mail.gmail.com here, and CCing the people involved there.

A temporary slot (logical or physical) whose creation fails inside a
subtransaction, with the error caught by a PL/pgSQL exception block,
is left behind. Such a slot stays until the session ends, a top-level
error happens in that session, or the user drops it. A temporary
logical slot holds WAL and the catalog xmin, and the same session can
decode partial transactions from it (data loss for logical decoding).
A temporary physical slot holds WAL.

Here is a reproducer for a temporary logical slot [2]DO $$ BEGIN PERFORM pg_create_logical_replication_slot('tmp_bad', 'no_such_plugin', true); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'caught %', SQLSTATE; END $$;. The slot is
left with restart_lsn and catalog_xmin set and confirmed_flush_lsn not
set. A slot in that state can still be used for decoding, and with no
confirmed_flush_lsn that can lose data (see the test case in the
attached patch). There is no start point, and since decoding from the
slot is not slot creation, the snapshot builder can restore a
serialized snapshot and become consistent in the middle of a
transaction that began before restart_lsn. Without the fix, the
healthy slot in the test returns the transaction with both inserts and
the leftover slot returns it with only the second one.

A temporary physical slot can also be left behind this way, but only
when writing its state file fails after the slot is allocated, for
example when the disk runs out of space.

The issue exists on HEAD and all supported branches back to PG14. The
fix in the other thread releases a slot that stays acquired after an
error is caught in a subtransaction. That release does not drop a
temporary slot whose creation failed. Even without that fix, on
non-assert builds the leftover slot just stays marked active and can
be decoded from, so the same data loss happens.

A few ways to fix this were discussed in the other thread.

1/ Mark temporary slots as ephemeral initially and transition them to
temporary once creation succeeds. Invasive. Not back-patchable.

2/ Introduce a new state to represent a temporary slot still in
creation (RS_TEMPORARY_EPHEMERAL or such). Invasive. Makes the code
complex. Not back-patchable.

3/ Use a boolean in the ReplicationSlot structure
(is_create_in_progress or such), and in the subxact callback, when the
slot is temporary and that boolean is set, drop just that slot and
leave the others alone. Not ABI compatible. Not back-patchable.

4/ At subxact abort, if the slot is a temporary logical slot and its
confirmed_flush is invalid, drop it. The drop needs to unset the
PROC_IN_LOGICAL_DECODING flag as ReplicationSlotRelease() does.

5/ Add a check to CreateDecodingContext() that raises an error if the
slot's confirmed_flush is invalid. On v17 and later, slot
synchronization needs to be excluded, since update_local_synced_slot()
advances a newly created synced slot via
LogicalSlotAdvanceAndCheckSnapState() before its confirmed_flush is
set.

6/ Track the in-progress creation with a backend-local variable
(something like MyReplicationSlotCreating), set when the slot is
created and reset when it is released. In the subxact callback, use it
to drop just the temporary slot being created. Back-patchable. Covers
both logical and physical temporary slots.

I prefer the backend-local tracking approach and attached a patch for
it on top of v19 from the other thread. Please have a look. I also
think it is good to tighten this up with the CreateDecodingContext()
check, so that a slot with an invalid confirmed_flush can never be
used for decoding.

Thoughts?

[1]: /messages/by-id/CAHg+QDeuf9tCq3ce=kgFMJP0m=PZC+wi6B=yS+7V0vNXjLS31w@mail.gmail.com

[2]: DO $$ BEGIN PERFORM pg_create_logical_replication_slot('tmp_bad', 'no_such_plugin', true); EXCEPTION WHEN OTHERS THEN RAISE NOTICE 'caught %', SQLSTATE; END $$;
DO $$
BEGIN
PERFORM pg_create_logical_replication_slot('tmp_bad', 'no_such_plugin', true);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'caught %', SQLSTATE;
END $$;

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com

Attachments:

v1-0001-Drop-a-temporary-replication-slot-if-its-creation.patchapplication/octet-stream; name=v1-0001-Drop-a-temporary-replication-slot-if-its-creation.patchDownload+200-18
#2Hayato Kuroda (Fujitsu)
kuroda.hayato@fujitsu.com
In reply to: Bharath Rupireddy (#1)
RE: Temporary slot leak when creation fails in a subtransaction

Dear Bharath,

Here is a reproducer for a temporary logical slot [2]. The slot is
left with restart_lsn and catalog_xmin set and confirmed_flush_lsn not
set. A slot in that state can still be used for decoding, and with no
confirmed_flush_lsn that can lose data (see the test case in the
attached patch). There is no start point, and since decoding from the
slot is not slot creation, the snapshot builder can restore a
serialized snapshot and become consistent in the middle of a
transaction that began before restart_lsn. Without the fix, the
healthy slot in the test returns the transaction with both inserts and
the leftover slot returns it with only the second one.

Confirmed that the temp slot could retain even after applying the premise patch.

A few ways to fix this were discussed in the other thread.

FYI, I considered an alternative: register to resource owner. My idea was to
register the replication slot while creating the slot, and release or drop it based
on the needs, but it seems just introducing complexity. Also releasing callback
must not perform operations could fail like memory allocation, so it's not appropriate.

Best regards,
Hayato Kuroda
FUJITSU LIMITED

#3shveta malik
shveta.malik@gmail.com
In reply to: Bharath Rupireddy (#1)
Re: Temporary slot leak when creation fails in a subtransaction

On Fri, Sep 25, 2026 at 11:09 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:

Hi,

Moving the discussion from [1] here, and CCing the people involved there.

A temporary slot (logical or physical) whose creation fails inside a
subtransaction, with the error caught by a PL/pgSQL exception block,
is left behind. Such a slot stays until the session ends, a top-level
error happens in that session, or the user drops it. A temporary
logical slot holds WAL and the catalog xmin, and the same session can
decode partial transactions from it (data loss for logical decoding).
A temporary physical slot holds WAL.

Here is a reproducer for a temporary logical slot [2]. The slot is
left with restart_lsn and catalog_xmin set and confirmed_flush_lsn not
set. A slot in that state can still be used for decoding, and with no
confirmed_flush_lsn that can lose data (see the test case in the
attached patch). There is no start point, and since decoding from the
slot is not slot creation, the snapshot builder can restore a
serialized snapshot and become consistent in the middle of a
transaction that began before restart_lsn. Without the fix, the
healthy slot in the test returns the transaction with both inserts and
the leftover slot returns it with only the second one.

A temporary physical slot can also be left behind this way, but only
when writing its state file fails after the slot is allocated, for
example when the disk runs out of space.

The issue exists on HEAD and all supported branches back to PG14. The
fix in the other thread releases a slot that stays acquired after an
error is caught in a subtransaction. That release does not drop a
temporary slot whose creation failed. Even without that fix, on
non-assert builds the leftover slot just stays marked active and can
be decoded from, so the same data loss happens.

A few ways to fix this were discussed in the other thread.

1/ Mark temporary slots as ephemeral initially and transition them to
temporary once creation succeeds. Invasive. Not back-patchable.

2/ Introduce a new state to represent a temporary slot still in
creation (RS_TEMPORARY_EPHEMERAL or such). Invasive. Makes the code
complex. Not back-patchable.

3/ Use a boolean in the ReplicationSlot structure
(is_create_in_progress or such), and in the subxact callback, when the
slot is temporary and that boolean is set, drop just that slot and
leave the others alone. Not ABI compatible. Not back-patchable.

4/ At subxact abort, if the slot is a temporary logical slot and its
confirmed_flush is invalid, drop it. The drop needs to unset the
PROC_IN_LOGICAL_DECODING flag as ReplicationSlotRelease() does.

5/ Add a check to CreateDecodingContext() that raises an error if the
slot's confirmed_flush is invalid. On v17 and later, slot
synchronization needs to be excluded, since update_local_synced_slot()
advances a newly created synced slot via
LogicalSlotAdvanceAndCheckSnapState() before its confirmed_flush is
set.

6/ Track the in-progress creation with a backend-local variable
(something like MyReplicationSlotCreating), set when the slot is
created and reset when it is released. In the subxact callback, use it
to drop just the temporary slot being created. Back-patchable. Covers
both logical and physical temporary slots.

I prefer the backend-local tracking approach and attached a patch for
it on top of v19 from the other thread. Please have a look. I also
think it is good to tighten this up with the CreateDecodingContext()
check, so that a slot with an invalid confirmed_flush can never be
used for decoding.

Thoughts?

[1] /messages/by-id/CAHg+QDeuf9tCq3ce=kgFMJP0m=PZC+wi6B=yS+7V0vNXjLS31w@mail.gmail.com

[2]
DO $$
BEGIN
PERFORM pg_create_logical_replication_slot('tmp_bad', 'no_such_plugin', true);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'caught %', SQLSTATE;
END $$;

Thanks for the patch Bharath. The patch does not apply to HEAD (even
on an older version dated 15 days ago). Can you please rebase it?

thanks
Shveta

#4shveta malik
shveta.malik@gmail.com
In reply to: shveta malik (#3)
Re: Temporary slot leak when creation fails in a subtransaction

On Tue, Sep 29, 2026 at 2:05 PM shveta malik <shveta.malik@gmail.com> wrote:

On Fri, Sep 25, 2026 at 11:09 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:

Hi,

Moving the discussion from [1] here, and CCing the people involved there.

A temporary slot (logical or physical) whose creation fails inside a
subtransaction, with the error caught by a PL/pgSQL exception block,
is left behind. Such a slot stays until the session ends, a top-level
error happens in that session, or the user drops it. A temporary
logical slot holds WAL and the catalog xmin, and the same session can
decode partial transactions from it (data loss for logical decoding).
A temporary physical slot holds WAL.

Here is a reproducer for a temporary logical slot [2]. The slot is
left with restart_lsn and catalog_xmin set and confirmed_flush_lsn not
set. A slot in that state can still be used for decoding, and with no
confirmed_flush_lsn that can lose data (see the test case in the
attached patch). There is no start point, and since decoding from the
slot is not slot creation, the snapshot builder can restore a
serialized snapshot and become consistent in the middle of a
transaction that began before restart_lsn. Without the fix, the
healthy slot in the test returns the transaction with both inserts and
the leftover slot returns it with only the second one.

A temporary physical slot can also be left behind this way, but only
when writing its state file fails after the slot is allocated, for
example when the disk runs out of space.

The issue exists on HEAD and all supported branches back to PG14. The
fix in the other thread releases a slot that stays acquired after an
error is caught in a subtransaction. That release does not drop a
temporary slot whose creation failed. Even without that fix, on
non-assert builds the leftover slot just stays marked active and can
be decoded from, so the same data loss happens.

A few ways to fix this were discussed in the other thread.

1/ Mark temporary slots as ephemeral initially and transition them to
temporary once creation succeeds. Invasive. Not back-patchable.

2/ Introduce a new state to represent a temporary slot still in
creation (RS_TEMPORARY_EPHEMERAL or such). Invasive. Makes the code
complex. Not back-patchable.

3/ Use a boolean in the ReplicationSlot structure
(is_create_in_progress or such), and in the subxact callback, when the
slot is temporary and that boolean is set, drop just that slot and
leave the others alone. Not ABI compatible. Not back-patchable.

4/ At subxact abort, if the slot is a temporary logical slot and its
confirmed_flush is invalid, drop it. The drop needs to unset the
PROC_IN_LOGICAL_DECODING flag as ReplicationSlotRelease() does.

5/ Add a check to CreateDecodingContext() that raises an error if the
slot's confirmed_flush is invalid. On v17 and later, slot
synchronization needs to be excluded, since update_local_synced_slot()
advances a newly created synced slot via
LogicalSlotAdvanceAndCheckSnapState() before its confirmed_flush is
set.

6/ Track the in-progress creation with a backend-local variable
(something like MyReplicationSlotCreating), set when the slot is
created and reset when it is released. In the subxact callback, use it
to drop just the temporary slot being created. Back-patchable. Covers
both logical and physical temporary slots.

I prefer the backend-local tracking approach and attached a patch for
it on top of v19 from the other thread. Please have a look. I also
think it is good to tighten this up with the CreateDecodingContext()
check, so that a slot with an invalid confirmed_flush can never be
used for decoding.

Thoughts?

[1] /messages/by-id/CAHg+QDeuf9tCq3ce=kgFMJP0m=PZC+wi6B=yS+7V0vNXjLS31w@mail.gmail.com

[2]
DO $$
BEGIN
PERFORM pg_create_logical_replication_slot('tmp_bad', 'no_such_plugin', true);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'caught %', SQLSTATE;
END $$;

Thanks for the patch Bharath. The patch does not apply to HEAD (even
on an older version dated 15 days ago). Can you please rebase it?

Kuroda-san informed me off-list that it applies atop PG19 patch shared
in another thread. I will review.
But one question: are we planning to push this fix too to PG19 and
thus the patch is this way? And then later we can do it on HEAD, is
that the plan?

thanks
Shveta

#5Narayanan Venkateswaran
narayananvpostgres@gmail.com
In reply to: shveta malik (#4)
Re: Temporary slot leak when creation fails in a subtransaction

Hi,

Can you please point me to the PG19 patch on which I need to apply the
patch shared on this thread ? I am interested in reviewing this work too.

Thank you,
Narayanan

On Tue, Sep 29, 2026 at 2:40 PM shveta malik <shveta.malik@gmail.com> wrote:

Show quoted text

On Tue, Sep 29, 2026 at 2:05 PM shveta malik <shveta.malik@gmail.com>
wrote:

On Fri, Sep 25, 2026 at 11:09 PM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:

Hi,

Moving the discussion from [1] here, and CCing the people involved

there.

A temporary slot (logical or physical) whose creation fails inside a
subtransaction, with the error caught by a PL/pgSQL exception block,
is left behind. Such a slot stays until the session ends, a top-level
error happens in that session, or the user drops it. A temporary
logical slot holds WAL and the catalog xmin, and the same session can
decode partial transactions from it (data loss for logical decoding).
A temporary physical slot holds WAL.

Here is a reproducer for a temporary logical slot [2]. The slot is
left with restart_lsn and catalog_xmin set and confirmed_flush_lsn not
set. A slot in that state can still be used for decoding, and with no
confirmed_flush_lsn that can lose data (see the test case in the
attached patch). There is no start point, and since decoding from the
slot is not slot creation, the snapshot builder can restore a
serialized snapshot and become consistent in the middle of a
transaction that began before restart_lsn. Without the fix, the
healthy slot in the test returns the transaction with both inserts and
the leftover slot returns it with only the second one.

A temporary physical slot can also be left behind this way, but only
when writing its state file fails after the slot is allocated, for
example when the disk runs out of space.

The issue exists on HEAD and all supported branches back to PG14. The
fix in the other thread releases a slot that stays acquired after an
error is caught in a subtransaction. That release does not drop a
temporary slot whose creation failed. Even without that fix, on
non-assert builds the leftover slot just stays marked active and can
be decoded from, so the same data loss happens.

A few ways to fix this were discussed in the other thread.

1/ Mark temporary slots as ephemeral initially and transition them to
temporary once creation succeeds. Invasive. Not back-patchable.

2/ Introduce a new state to represent a temporary slot still in
creation (RS_TEMPORARY_EPHEMERAL or such). Invasive. Makes the code
complex. Not back-patchable.

3/ Use a boolean in the ReplicationSlot structure
(is_create_in_progress or such), and in the subxact callback, when the
slot is temporary and that boolean is set, drop just that slot and
leave the others alone. Not ABI compatible. Not back-patchable.

4/ At subxact abort, if the slot is a temporary logical slot and its
confirmed_flush is invalid, drop it. The drop needs to unset the
PROC_IN_LOGICAL_DECODING flag as ReplicationSlotRelease() does.

5/ Add a check to CreateDecodingContext() that raises an error if the
slot's confirmed_flush is invalid. On v17 and later, slot
synchronization needs to be excluded, since update_local_synced_slot()
advances a newly created synced slot via
LogicalSlotAdvanceAndCheckSnapState() before its confirmed_flush is
set.

6/ Track the in-progress creation with a backend-local variable
(something like MyReplicationSlotCreating), set when the slot is
created and reset when it is released. In the subxact callback, use it
to drop just the temporary slot being created. Back-patchable. Covers
both logical and physical temporary slots.

I prefer the backend-local tracking approach and attached a patch for
it on top of v19 from the other thread. Please have a look. I also
think it is good to tighten this up with the CreateDecodingContext()
check, so that a slot with an invalid confirmed_flush can never be
used for decoding.

Thoughts?

[1]

/messages/by-id/CAHg+QDeuf9tCq3ce=kgFMJP0m=PZC+wi6B=yS+7V0vNXjLS31w@mail.gmail.com

[2]
DO $$
BEGIN
PERFORM pg_create_logical_replication_slot('tmp_bad',

'no_such_plugin', true);

EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'caught %', SQLSTATE;
END $$;

Thanks for the patch Bharath. The patch does not apply to HEAD (even
on an older version dated 15 days ago). Can you please rebase it?

Kuroda-san informed me off-list that it applies atop PG19 patch shared
in another thread. I will review.
But one question: are we planning to push this fix too to PG19 and
thus the patch is this way? And then later we can do it on HEAD, is
that the plan?

thanks
Shveta

#6Bharath Rupireddy
bharath.rupireddyforpostgres@gmail.com
In reply to: shveta malik (#4)
Re: Temporary slot leak when creation fails in a subtransaction

Hi,

On Tue, Sep 29, 2026 at 2:10 AM shveta malik <shveta.malik@gmail.com> wrote:

Kuroda-san informed me off-list that it applies atop PG19 patch shared
in another thread. I will review.

Thanks. The v1 patch attached here was built on top of the v19 patch
from the other thread -
/messages/by-id/CALj2ACXtHqVRBb40OHXx1JAMnv-L=-PFU_nyk2aB=0wMgkpGOw@mail.gmail.com.
Apologies for not being clear about that in the initial email.

But one question: are we planning to push this fix too to PG19 and
thus the patch is this way? And then later we can do it on HEAD, is
that the plan?

Thanks Kuroda-san for sharing the resource owner (resowner) idea. But
I don't think it fits well here. A slot can outlive the
(sub)transaction that created it, in which case dropping it at the end
of that (sub)transaction with a resowner might complicate things. It
may not work for the walsender, which creates slots without a
transaction. It also requires duplicating or moving some of the slot
release code we have today in the shmem exit callback, sigsetjmp
blocks etc. into the resowner callback. Also, logical decoding
internally starts its own (sub)transactions, which makes the resowner
approach a bit harder. IMHO, that feels more invasive than the fix
needs, even on HEAD.

So I still prefer the backend-local flag from v1, for both HEAD and
the back branches. It also handles both logical and physical temporary
slots, which a few of the other approaches mentioned upthread don't.

While at it, I found another case. A persistent physical slot whose
creation fails is also left behind, with or without a subtransaction,
since release does not drop it. v1 does not handle that yet, but the
same flag can cover it. Another approach is to create them as
ephemeral first and persist them once creation succeeds, as logical
slots do. Although physical slot creation failures are rare, I think
it is better to handle them as well to make the creation failure fixes
complete.

Thoughts?

--
Bharath Rupireddy
Amazon Web Services: https://aws.amazon.com

#7shveta malik
shveta.malik@gmail.com
In reply to: Bharath Rupireddy (#6)
Re: Temporary slot leak when creation fails in a subtransaction

On Wed, Sep 30, 2026 at 5:52 AM Bharath Rupireddy
<bharath.rupireddyforpostgres@gmail.com> wrote:

Hi,

On Tue, Sep 29, 2026 at 2:10 AM shveta malik <shveta.malik@gmail.com> wrote:

Kuroda-san informed me off-list that it applies atop PG19 patch shared
in another thread. I will review.

Thanks. The v1 patch attached here was built on top of the v19 patch
from the other thread -
/messages/by-id/CALj2ACXtHqVRBb40OHXx1JAMnv-L=-PFU_nyk2aB=0wMgkpGOw@mail.gmail.com.
Apologies for not being clear about that in the initial email.

But one question: are we planning to push this fix too to PG19 and
thus the patch is this way? And then later we can do it on HEAD, is
that the plan?

Thanks Kuroda-san for sharing the resource owner (resowner) idea. But
I don't think it fits well here. A slot can outlive the
(sub)transaction that created it, in which case dropping it at the end
of that (sub)transaction with a resowner might complicate things. It
may not work for the walsender, which creates slots without a
transaction. It also requires duplicating or moving some of the slot
release code we have today in the shmem exit callback, sigsetjmp
blocks etc. into the resowner callback. Also, logical decoding
internally starts its own (sub)transactions, which makes the resowner
approach a bit harder. IMHO, that feels more invasive than the fix
needs, even on HEAD.

So I still prefer the backend-local flag from v1, for both HEAD and
the back branches. It also handles both logical and physical temporary
slots, which a few of the other approaches mentioned upthread don't.

While at it, I found another case. A persistent physical slot whose
creation fails is also left behind, with or without a subtransaction,
since release does not drop it.

yes, this is an existing issue as also raised by Sawada-san in [1]/messages/by-id/CAD21AoC0UUHAWSA9TWBmBQkSt4v6QqhmE6HjNTVo55XgqTkG-g@mail.gmail.com.

v1 does not handle that yet, but the
same flag can cover it. Another approach is to create them as
ephemeral first and persist them once creation succeeds, as logical
slots do.

My preference even earlier was this approach: create them as EPHEMERAL
and persist them upon successful creation, but as Sawada-san
suggested, this should be done only on HEAD.

See 'Some hackers including me prefer option 1 but we agreed it
should be only master as it's impactful.' at [2]/messages/by-id/CAD21AoDTAv8Py3q=JC5n3w=Y1z-jU15DnU8d3dhP7JEwMZR2CQ@mail.gmail.com

So could it be that we use the static variable approach on back
branches but handle it more gracefully on HEAD using the EPHEMERAL
intermediate state? Or do you think that fix is not invasive and can
also be tried on back-branches?

Although physical slot creation failures are rare, I think
it is better to handle them as well to make the creation failure fixes
complete.

I agree.

[1]: /messages/by-id/CAD21AoC0UUHAWSA9TWBmBQkSt4v6QqhmE6HjNTVo55XgqTkG-g@mail.gmail.com
[2]: /messages/by-id/CAD21AoDTAv8Py3q=JC5n3w=Y1z-jU15DnU8d3dhP7JEwMZR2CQ@mail.gmail.com

thanks
Shveta