postgres.git / summary / log / commit / refs

commit    a4eb59d40f278471d5b83a25cef6e56bc997e696
Author:   Fujii Masao <fujii@postgresql.org>
Date:     Wed Jun 03 09:47:31 2026 +0000

    Fix race in ReplicationSlotRelease() for ephemeral slots
    
    When releasing an ephemeral replication slot, ReplicationSlotRelease()
    drops the slot via ReplicationSlotDropAcquired().
    
    However, after dropping the slot, ReplicationSlotRelease() continued
    to use its local "slot" pointer, which still referenced the dropped
    slot's former shared-memory entry. It could then update fields such as
    effective_xmin in that entry.
    
    Once an ephemeral slot has been dropped (via ReplicationSlotDropAcquired()),
    its slot array entry can be reused immediately by another backend
    creating a new slot. As a result, those updates could corrupt
    the state of an unrelated replication slot.
    
    Fix by skipping those shared-memory updates for phemeral slots and
    performing them only for non-ephemeral slots, whose shared-memory
    entries remain valid after release.
    
    Backpatch to all supported versions.
    
    Author: Zhijie Hou <houzj.fnst@fujitsu.com>
    Reviewed-by: Masao Fujii <masao.fujii@gmail.com>
    Reviewed-by: Srinath Reddy Sadipiralla <srinath2133@gmail.com>
    Reviewed-by: Xuneng Zhou <xunengzhou@gmail.com>
    Discussion: https://postgr.es/m/TY4PR01MB177184FF9EE916F577E1F554194082@TY4PR01MB17718.jpnprd01.prod.outlook.com
    Backpatch-through: 14


src/backend/replication/slot.c | 52 ++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 1f4aad52c4a..6e3a561691a 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -587,35 +587,37 @@ ReplicationSlotRelease(void) */ ReplicationSlotDropAcquired(); } - - /* - * If slot needed to temporarily restrain both data and catalog xmin to - * create the catalog snapshot, remove that temporary constraint. - * Snapshots can only be exported while the initial snapshot is still - * acquired. - */ - if (!TransactionIdIsValid(slot->data.xmin) && - TransactionIdIsValid(slot->effective_xmin)) - { - SpinLockAcquire(&slot->mutex); - slot->effective_xmin = InvalidTransactionId; - SpinLockRelease(&slot->mutex); - ReplicationSlotsComputeRequiredXmin(false); - } - - if (slot->data.persistency == RS_PERSISTENT) + else { /* - * Mark persistent slot inactive. We're not freeing it, just - * disconnecting, but wake up others that may be waiting for it. + * If slot needed to temporarily restrain both data and catalog xmin + * to create the catalog snapshot, remove that temporary constraint. + * Snapshots can only be exported while the initial snapshot is still + * acquired. */ - SpinLockAcquire(&slot->mutex); - slot->active_pid = 0; - SpinLockRelease(&slot->mutex); - ConditionVariableBroadcast(&slot->active_cv); - } + if (!TransactionIdIsValid(slot->data.xmin) && + TransactionIdIsValid(slot->effective_xmin)) + { + SpinLockAcquire(&slot->mutex); + slot->effective_xmin = InvalidTransactionId; + SpinLockRelease(&slot->mutex); + ReplicationSlotsComputeRequiredXmin(false); + } - MyReplicationSlot = NULL; + if (slot->data.persistency == RS_PERSISTENT) + { + /* + * Mark persistent slot inactive. We're not freeing it, just + * disconnecting, but wake up others that may be waiting for it. + */ + SpinLockAcquire(&slot->mutex); + slot->active_pid = 0; + SpinLockRelease(&slot->mutex); + ConditionVariableBroadcast(&slot->active_cv); + } + + MyReplicationSlot = NULL; + } /* might not have been set when we've been a plain slot */ LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); [parent: 259b627d5ccb]