v26-0002-FIXUP-remove-status_change_allowed-flag.patch
application/octet-stream
Filename: v26-0002-FIXUP-remove-status_change_allowed-flag.patch
Type: application/octet-stream
Part: 1
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v26-0002
Subject: FIXUP: remove status_change_allowed flag.
| File | + | − |
|---|---|---|
| src/backend/access/transam/xlog.c | 5 | 1 |
| src/backend/replication/logical/logical.c | 2 | 14 |
| src/backend/replication/logical/logicalctl.c | 121 | 130 |
| src/backend/replication/slot.c | 3 | 0 |
| src/include/replication/logicalctl.h | 2 | 2 |
| src/test/recovery/t/050_effective_wal_level.pl | 0 | 3 |
From 183c693210a66f43cadd97edba620e8df5d413c5 Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 13 Nov 2025 14:42:40 -0800
Subject: [PATCH v26 2/2] FIXUP: remove status_change_allowed flag.
---
src/backend/access/transam/xlog.c | 6 +-
src/backend/replication/logical/logical.c | 16 +-
src/backend/replication/logical/logicalctl.c | 251 +++++++++---------
src/backend/replication/slot.c | 3 +
src/include/replication/logicalctl.h | 4 +-
.../recovery/t/050_effective_wal_level.pl | 3 -
6 files changed, 133 insertions(+), 150 deletions(-)
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 5a16c1e520c..14226cd19a7 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -5522,6 +5522,7 @@ StartupXLOG(void)
XLogRecPtr missingContrecPtr;
TransactionId oldestActiveXID;
bool promoted = false;
+ bool logicaldec_status_changed;
char timebuf[128];
/*
@@ -6249,7 +6250,7 @@ StartupXLOG(void)
* startup should not do any operations that wait for the checkpointer
* because otherwise it easily ends up with a deadlock.
*/
- UpdateLogicalDecodingStatusEndOfRecovery();
+ logicaldec_status_changed = UpdateLogicalDecodingStatusEndOfRecovery();
/* Clean up EndOfWalRecoveryInfo data to appease Valgrind leak checking */
if (endOfRecoveryInfo->lastPage)
@@ -6282,6 +6283,9 @@ StartupXLOG(void)
UpdateControlFile();
LWLockRelease(ControlFileLock);
+ if (logicaldec_status_changed)
+ CompleteLogicalDecodingStatusUpdateEndOfRecovery();
+
/*
* Wake up all waiters for replay LSN. They need to report an error that
* recovery was ended before reaching the target LSN.
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 7a5a86313b6..f4366f59ee7 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -125,20 +125,8 @@ CheckLogicalDecodingRequirements(void)
/* CheckSlotRequirements() has already checked if wal_level >= 'replica' */
- /*
- * Check if logical decoding is available on standbys. Typically, when
- * running a standby, RecoveryInProgress() returning true implies that
- * LogicalDecodingStatusChangeAllowed() is false. However, during
- * promotion, there is a brief transitional phase where
- * RecoveryInProgress() remains true even though
- * LogicalDecodingStatusChangeAllowed() has already turned true.
- *
- * In this window, logical decoding enable/disable operations are
- * permitted on standby, anticipating its transition to primary. The
- * actual wait for recovery completion is handled within
- * start_logical_decoding_status_change().
- */
- if (!IsLogicalDecodingEnabled() && !LogicalDecodingStatusChangeAllowed())
+ /* Check if logical decoding is available on standby */
+ if (RecoveryInProgress() && !IsLogicalDecodingEnabled())
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("logical decoding on standby requires \"effective_wal_level\" >= \"logical\" on the primary"),
diff --git a/src/backend/replication/logical/logicalctl.c b/src/backend/replication/logical/logicalctl.c
index 57ba46bd204..be079fb8e40 100644
--- a/src/backend/replication/logical/logicalctl.c
+++ b/src/backend/replication/logical/logicalctl.c
@@ -96,26 +96,6 @@ typedef struct LogicalDecodingCtlData
/* True if logical decoding is available in the system */
bool logical_decoding_enabled;
- /*
- * This flag indicates whether logical decoding status changes are
- * allowed. It is false during recovery and becomes true when recovery
- * ends. Even when true, it specifically means "allowed after recovery has
- * fully completed".
- *
- * This flag helps prevent race conditions with the startup process's
- * end-of-recovery actions. After the startup process updates the logical
- * decoding status at recovery end, other processes might attempt to
- * toggle logical decoding before recovery fully completes (i.e.,
- * RecoveryInProgress() returns false) - a period when WAL writes are
- * still not permitted. Therefore, when this flag is true, we must wait
- * for recovery to fully complete before attempting an activation or a
- * deactivation. We cannot rely on the end-of-recovery to allow toggling
- * of the logical decoding status because it's possible that concurrent
- * processes write non-logical WAL records after the startup process
- * enables logical decoding.
- */
- bool status_change_allowed;
-
/* True while the logical decoding status is being changed */
bool status_change_inprogress;
@@ -138,6 +118,7 @@ bool XLogLogicalInfo = false;
static void update_xlog_logical_info(void);
static void abort_logical_decoding_activation(int code, Datum arg);
static bool start_logical_decoding_status_change(bool new_status);
+static void write_logical_decoding_status_update_record(bool status);
Size
LogicalDecodingCtlShmemSize(void)
@@ -201,7 +182,7 @@ InitializeProcessXLogLogicalInfo(void)
}
/*
- * This routine is called when we are ordered to update XLogLogicalInfo
+ * This routine is called when we are told to update XLogLogicalInfo
* by a ProcSignalBarrier.
*/
bool
@@ -246,21 +227,20 @@ IsXLogLogicalInfoEnabled(void)
/*
* Enable or disable both the status of logical info WAL logging and logical
* decoding in shmem.
-
+ *
* Note that this function updates the global flags without the state transition
* process. EnsureLogicalDecodingEnabled() and DisableLogicalDecodingIfNecessary()
* should be used instead if there could be concurrent processes doing writes
- * or logical decoding, particularly once the status change is allowed globally.
+ * or logical decoding, particularly after recovery completes.
*/
void
UpdateLogicalDecodingStatus(bool new_status, bool need_lock)
{
+ Assert(RecoveryInProgress());
+
if (need_lock)
LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
- /* Must be called before allowing the status change globally */
- Assert(!LogicalDecodingCtl->status_change_allowed);
-
LogicalDecodingCtl->xlog_logical_info = new_status;
LogicalDecodingCtl->logical_decoding_enabled = new_status;
@@ -270,6 +250,20 @@ UpdateLogicalDecodingStatus(bool new_status, bool need_lock)
elog(DEBUG1, "update logical decoding status to %d", new_status);
}
+/*
+ * Writes XLOG_LOGICAL_DECODING_STATUS_CHANGE WAL record with the given status.
+ */
+static void
+write_logical_decoding_status_update_record(bool status)
+{
+ XLogRecPtr recptr;
+
+ XLogBeginInsert();
+ XLogRegisterData(&status, sizeof(bool));
+ recptr = XLogInsert(RM_XLOG_ID, XLOG_LOGICAL_DECODING_STATUS_CHANGE);
+ XLogFlush(recptr);
+}
+
/*
* A PG_ENSURE_ERROR_CLEANUP callback for activating logical decoding, resetting
* the shared flags to revert the logical decoding activation process.
@@ -302,62 +296,15 @@ abort_logical_decoding_activation(int code, Datum arg)
ConditionVariableBroadcast(&LogicalDecodingCtl->cv);
}
-/*
- * Returns the status_change_allowed flag in LogicalDecodingCtl. The caller
- * might need to check RecoveryInProgress() as well. Please see the comments for
- * the status_change_allowed flag for details.
- */
-bool
-LogicalDecodingStatusChangeAllowed(void)
-{
- bool status_change_allowed;
-
- LWLockAcquire(LogicalDecodingControlLock, LW_SHARED);
- status_change_allowed = LogicalDecodingCtl->status_change_allowed;
- LWLockRelease(LogicalDecodingControlLock);
-
- return status_change_allowed;
-}
-
/*
* Performs preparation work required before changing the logical decoding
* status. If the status change is required, it sets
* LogicalDecodingCtl->status_change_inprogress, and returns true. Otherwise,
- * if it's not required or not allowed (e.g., logical slots exist) it returns
- * false.
+ * if it's not required (e.g., logical slots exist) it returns false.
*/
static bool
start_logical_decoding_status_change(bool new_status)
{
- if (!LogicalDecodingStatusChangeAllowed())
- return false;
-
- if (RecoveryInProgress())
- {
- /*
- * Wait for the recovery to complete. Note that even the checkpointer
- * can wait for the recovery to complete here without concerning
- * deadlocks unless the startup process performs any action that waits
- * for it after calling UpdateLogicalDecodingStatusEndOfRecovery().
- */
- elog(DEBUG1,
- "waiting for recovery completion to change logical decoding status");
- do
- {
- CHECK_FOR_INTERRUPTS();
-
- pgstat_report_wait_start(WAIT_EVENT_LOGICAL_DECODING_STATUS_CHANGE_DELAY);
- pg_usleep(100000L); /* wait for 100 msec */
- pgstat_report_wait_end();
- }
- while (RecoveryInProgress());
-
- /*
- * Now that writing WAL records are officially allowed, start the
- * logical decoding status change.
- */
- }
-
retry:
LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
@@ -366,6 +313,10 @@ retry:
{
/* Release the lock and wait for someone to complete the transition */
LWLockRelease(LogicalDecodingControlLock);
+
+ elog(DEBUG1,
+ "waiting for logical decoding status change to complete");
+
ConditionVariableSleep(&LogicalDecodingCtl->cv,
WAIT_EVENT_LOGICAL_DECODING_STATUS_CHANGE);
@@ -405,7 +356,7 @@ retry:
* If this function is called during recovery, it simply returns without
* action since the logical decoding status change is not allowed during
* this time. The logical decoding status depends on the status on the primary.
- * The caller can use CheckLogicalDecodingRequirements() before calling this
+ * The caller should use CheckLogicalDecodingRequirements() before calling this
* function to make sure that the logical decoding status can be modified.
*
* Note that there is no interlock between logical decoding activation
@@ -427,6 +378,12 @@ EnsureLogicalDecodingEnabled(void)
if (wal_level >= WAL_LEVEL_LOGICAL)
return;
+ if (RecoveryInProgress())
+ {
+ Assert(IsLogicalDecodingEnabled());
+ return;
+ }
+
/* Prepare and start the activation process if it's disabled */
if (!start_logical_decoding_status_change(true))
return;
@@ -477,15 +434,7 @@ EnsureLogicalDecodingEnabled(void)
LogicalDecodingCtl->logical_decoding_enabled = true;
LWLockRelease(LogicalDecodingControlLock);
- {
- XLogRecPtr recptr;
- bool logical_decoding = true;
-
- XLogBeginInsert();
- XLogRegisterData(&logical_decoding, sizeof(bool));
- recptr = XLogInsert(RM_XLOG_ID, XLOG_LOGICAL_DECODING_STATUS_CHANGE);
- XLogFlush(recptr);
- }
+ write_logical_decoding_status_update_record(true);
/* Complete the transition */
LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
@@ -517,13 +466,6 @@ RequestDisableLogicalDecoding(void)
if (wal_level != WAL_LEVEL_REPLICA)
return;
- /*
- * Check if the status change is allowed before initiating a disable
- * request, to avoid unnecessary work.
- */
- if (!LogicalDecodingStatusChangeAllowed())
- return;
-
/*
* It's possible that we might not actually need to disable logical
* decoding if someone creates a new logical slot concurrently. We set the
@@ -561,6 +503,9 @@ DisableLogicalDecodingIfNecessary(void)
*/
Assert(!MyReplicationSlot);
+ if (RecoveryInProgress())
+ return;
+
LWLockAcquire(LogicalDecodingControlLock, LW_SHARED);
pending_disable = LogicalDecodingCtl->pending_disable;
LWLockRelease(LogicalDecodingControlLock);
@@ -592,15 +537,7 @@ DisableLogicalDecodingIfNecessary(void)
/* Write the WAL to disable logical decoding on standbys too */
if (XLogStandbyInfoActive())
- {
- bool logical_decoding = false;
- XLogRecPtr recptr;
-
- XLogBeginInsert();
- XLogRegisterData(&logical_decoding, sizeof(bool));
- recptr = XLogInsert(RM_XLOG_ID, XLOG_LOGICAL_DECODING_STATUS_CHANGE);
- XLogFlush(recptr);
- }
+ write_logical_decoding_status_update_record(false);
/* Now disable logical information WAL logging */
LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
@@ -633,24 +570,33 @@ DisableLogicalDecodingIfNecessary(void)
}
/*
- * Update the logical decoding status at end of the recovery. This function
- * must be called before accepting writes. Please refer to the comment of
- * LogicalDecodingCtlData.status_change_allowed flag for the details.
+ * Update the logical decoding status at end of recovery, and ensure all
+ * running processes have the updated XLogLogicalInfo status. This function
+ * must be called before accepting writes.
+ *
+ * Returns true if the logical decoding status is changed, requiring for
+ * the caller to finish the status transition, by calling
+ * by calling CompleteLogicalDecodingStatusUpdateEndOfRecovery(), after
+ * recovery fully completes.
*/
-void
+bool
UpdateLogicalDecodingStatusEndOfRecovery(void)
{
+ bool status_changed = false;
bool new_status = false;
- bool need_wal = false;
Assert(RecoveryInProgress());
Assert(!LogicalDecodingCtl->status_change_inprogress);
- /* With 'minimal' WAL level, logical decoding is always disabled */
+ /*
+ * With 'minimal' WAL level, there have not been logical slots during
+ * recovery. Logical decoding is always disabled, and no need to
+ * synchronize XLogLogicalInfo.
+ */
if (wal_level == WAL_LEVEL_MINIMAL)
{
Assert(!IsXLogLogicalInfoEnabled() && !IsLogicalDecodingEnabled());
- return;
+ return false;
}
LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
@@ -658,39 +604,53 @@ UpdateLogicalDecodingStatusEndOfRecovery(void)
if (wal_level == WAL_LEVEL_LOGICAL || CheckLogicalSlotExists())
new_status = true;
- if (LogicalDecodingCtl->logical_decoding_enabled != new_status)
- need_wal = true;
-
/*
- * Update shmem flags. We don't need to care about the order of setting
- * global flag and writing the WAL record as writes are not allowed yet.
+ * Check if the logical decoding status needs to updated. We don't need to
+ * check the status_change_inprogress since the status change is allowed
+ * after the recovery fully completes.
*/
- UpdateLogicalDecodingStatus(new_status, false);
-
- /*
- * Mark the end-of-recovery action has been done, allowing processes to
- * change the logical decoding status after the recovery finished.
- */
- LogicalDecodingCtl->status_change_allowed = true;
-
- LWLockRelease(LogicalDecodingControlLock);
-
- if (need_wal)
+ if (LogicalDecodingCtl->logical_decoding_enabled != new_status)
{
- XLogRecPtr recptr;
+ /*
+ * Update both the logical decoding status and logical WAL logging
+ * status. Unlike toggling these status during non-recovery, we don't
+ * need to worry about the operation order as WAL writes are still not
+ * permitted. Similarly, we don't need PG_ENSURE_ERROR_CLEANUP() to
+ * abort the status change process neither, as erroring out during
+ * recovery leads to a server shutdown.
+ */
+ LogicalDecodingCtl->status_change_inprogress = true;
+ UpdateLogicalDecodingStatus(new_status, false);
+
+ /*
+ * Now that we updated the logical decoding status, clear the pending
+ * disable flag. It's possible that a concurrent process drops the
+ * last logical slot and initiates the pending disable again. We will
+ * check the flag and wake up the checkpointer process at the
+ * completion step later.
+ */
+ LogicalDecodingCtl->pending_disable = false;
- Assert(XLogStandbyInfoActive());
+ write_logical_decoding_status_update_record(new_status);
- XLogBeginInsert();
- XLogRegisterData(&new_status, sizeof(bool));
- recptr = XLogInsert(RM_XLOG_ID, XLOG_LOGICAL_DECODING_STATUS_CHANGE);
- XLogFlush(recptr);
+ /*
+ * Note that we don't complete the status change by clearing the
+ * status_change_inprogress flag because other process might attempt
+ * to toggle logical decoding before recovery fully completes (i.e.
+ * RecoveryInProgress() returns false) - a period when WAL writes are
+ * still not permitted. We let the caller know the status change needs
+ * to be completed after recovery completes.
+ */
+ status_changed = true;
}
+ LWLockRelease(LogicalDecodingControlLock);
+
/*
* Ensure all running processes have the updated status. We don't need to
* wait for running transactions to finish as we don't accept any writes
- * yet. We need the wait even if we've not updated the status above as the
+ * yet. On the other hand, we need to wait for synchronizing
+ * XLogLogicalInfo even if we've not updated the status above as the
* status have been turned on and off during recovery, having running
* processes have different status on their local caches.
*/
@@ -699,4 +659,35 @@ UpdateLogicalDecodingStatusEndOfRecovery(void)
EmitProcSignalBarrier(PROCSIGNAL_BARRIER_UPDATE_XLOG_LOGICAL_INFO));
INJECTION_POINT("startup-logical-decoding-status-change-end-of-recovery", NULL);
+
+ return status_changed;
+}
+
+/*
+ * Complete the end-of-recovery logical decoding status update.
+ */
+void
+CompleteLogicalDecodingStatusUpdateEndOfRecovery(void)
+{
+ bool pending_disable;
+ volatile PROC_HDR *procglobal = ProcGlobal;
+ ProcNumber checkpointerProc = procglobal->checkpointerProc;
+
+ Assert(LogicalDecodingCtl->status_change_inprogress);
+
+ LWLockAcquire(LogicalDecodingControlLock, LW_EXCLUSIVE);
+ LogicalDecodingCtl->status_change_inprogress = false;
+ pending_disable = LogicalDecodingCtl->pending_disable;
+ LWLockRelease(LogicalDecodingControlLock);
+
+ /* Let waiters know the work finished */
+ ConditionVariableBroadcast(&LogicalDecodingCtl->cv);
+
+ /*
+ * If a concurrent process initiates a disable request since the
+ * end-of-recovery logical decoding status update, wake up the
+ * checkpointer.
+ */
+ if (pending_disable && checkpointerProc != INVALID_PROC_NUMBER)
+ SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c0d02ffa624..1da843119a1 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -2153,6 +2153,9 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
* causes in a single pass, minimizing redundant iterations. The "cause"
* parameter can be a MASK representing one or more of the defined causes.
*
+ * If it invalidates the last logical slot in the cluster, it requests to
+ * disable logical decoding.
+ *
* NB - this runs as part of checkpoint, so avoid raising errors if possible.
*/
bool
diff --git a/src/include/replication/logicalctl.h b/src/include/replication/logicalctl.h
index fe8b7141af7..d61f4b240a2 100644
--- a/src/include/replication/logicalctl.h
+++ b/src/include/replication/logicalctl.h
@@ -25,7 +25,7 @@ extern void EnsureLogicalDecodingEnabled(void);
extern void RequestDisableLogicalDecoding(void);
extern void DisableLogicalDecodingIfNecessary(void);
extern void UpdateLogicalDecodingStatus(bool new_status, bool need_lock);
-extern void UpdateLogicalDecodingStatusEndOfRecovery(void);
-extern bool LogicalDecodingStatusChangeAllowed(void);
+extern bool UpdateLogicalDecodingStatusEndOfRecovery(void);
+extern void CompleteLogicalDecodingStatusUpdateEndOfRecovery(void);
#endif
diff --git a/src/test/recovery/t/050_effective_wal_level.pl b/src/test/recovery/t/050_effective_wal_level.pl
index 9a04b653bdc..52a9ba054eb 100644
--- a/src/test/recovery/t/050_effective_wal_level.pl
+++ b/src/test/recovery/t/050_effective_wal_level.pl
@@ -364,9 +364,6 @@ if ( $ENV{enable_injection_points} eq 'yes'
$standby5->safe_psql('postgres',
qq[select pg_drop_replication_slot('standby5_slot');]);
- $standby5->wait_for_log(
- "waiting for recovery completion to change logical decoding status");
-
# Resume the startup process to complete the recovery.
$standby5->safe_psql('postgres',
qq[select injection_points_wakeup('startup-logical-decoding-status-change-end-of-recovery')]
--
2.47.3