From a605ebb3a6dc18b17ca1f7886dc007627b6cda0e Mon Sep 17 00:00:00 2001
From: Imran Zaheer <imran.zhir@gmail.com>
Date: Sat, 23 May 2026 14:02:04 +0500
Subject: [PATCH v2] Disable logical decoding after REPACK (CONCURRENTLY)

REPACK (CONCURRENTLY) drops a temporary logical replication slot but
never calls RequestDisableLogicalDecoding(), so effective_wal_level
remains stuck at 'logical'.

Add a disable_logical_decoding flag to ReplicationSlotDropAcquired()
so callers explicitly control this behavior.
---
 src/backend/commands/repack_worker.c          |  2 +-
 src/backend/replication/logical/launcher.c    |  2 +-
 src/backend/replication/logical/slotsync.c    |  2 +-
 src/backend/replication/slot.c                | 25 ++++++++-----------
 src/include/replication/slot.h                |  2 +-
 .../recovery/t/051_effective_wal_level.pl     | 14 +++++++++++
 6 files changed, 29 insertions(+), 18 deletions(-)

diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index b84041372b8..4f82eb46bec 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -323,7 +323,7 @@ repack_cleanup_logical_decoding(LogicalDecodingContext *ctx)
 		ExecDropSingleTupleTableSlot(dstate->slot);
 
 	FreeDecodingContext(ctx);
-	ReplicationSlotDropAcquired();
+	ReplicationSlotDropAcquired(true);
 }
 
 /*
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 50051dea8c7..137da582fe2 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1406,7 +1406,7 @@ ApplyLauncherMain(Datum main_arg)
 		if (MyReplicationSlot)
 		{
 			if (!retain_dead_tuples)
-				ReplicationSlotDropAcquired();
+				ReplicationSlotDropAcquired(false);
 			else if (can_update_xmin)
 				update_conflict_slot_xmin(xmin);
 		}
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index ad3747e598c..fe09ee04b6e 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -567,7 +567,7 @@ drop_local_obsolete_slots(List *remote_slot_list)
 			if (synced_slot)
 			{
 				ReplicationSlotAcquire(NameStr(local_slot->data.name), true, false);
-				ReplicationSlotDropAcquired();
+				ReplicationSlotDropAcquired(false);
 			}
 
 			UnlockSharedObject(DatabaseRelationId, local_slot->data.database,
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 83fcde74718..a87ef9dd82b 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -786,16 +786,12 @@ ReplicationSlotRelease(void)
 		 * Delete the slot. There is no !PANIC case where this is allowed to
 		 * fail, all that may happen is an incomplete cleanup of the on-disk
 		 * data.
-		 */
-		ReplicationSlotDropAcquired();
-
-		/*
-		 * Request to disable logical decoding, even though this slot may not
-		 * have been the last logical slot. The checkpointer will verify if
+		 *
+		 * Also request to disable logical decoding, even though this slot may
+		 * not have been the last logical slot. The checkpointer will verify if
 		 * logical decoding should actually be disabled.
 		 */
-		if (is_logical)
-			RequestDisableLogicalDecoding();
+		ReplicationSlotDropAcquired(is_logical);
 	}
 
 	/*
@@ -937,10 +933,7 @@ ReplicationSlotDrop(const char *name, bool nowait)
 
 	is_logical = SlotIsLogical(MyReplicationSlot);
 
-	ReplicationSlotDropAcquired();
-
-	if (is_logical)
-		RequestDisableLogicalDecoding();
+	ReplicationSlotDropAcquired(is_logical);
 }
 
 /*
@@ -1039,7 +1032,7 @@ ReplicationSlotAlter(const char *name, const bool *failover,
  * Permanently drop the currently acquired replication slot.
  */
 void
-ReplicationSlotDropAcquired(void)
+ReplicationSlotDropAcquired(bool disable_logical_decoding)
 {
 	ReplicationSlot *slot = MyReplicationSlot;
 
@@ -1049,6 +1042,10 @@ ReplicationSlotDropAcquired(void)
 	MyReplicationSlot = NULL;
 
 	ReplicationSlotDropPtr(slot);
+
+	/* Request checkpointer to disable the logical decoding */
+	if (disable_logical_decoding)
+		RequestDisableLogicalDecoding();
 }
 
 /*
@@ -1606,7 +1603,7 @@ restart:
 		 * beginning each time we release the lock.
 		 */
 		LWLockRelease(ReplicationSlotControlLock);
-		ReplicationSlotDropAcquired();
+		ReplicationSlotDropAcquired(false);
 		dropped = true;
 		goto restart;
 	}
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 77c8d0975b6..55e66e827a6 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -335,7 +335,7 @@ extern void ReplicationSlotCreate(const char *name, bool db_specific,
 								  bool synced);
 extern void ReplicationSlotPersist(void);
 extern void ReplicationSlotDrop(const char *name, bool nowait);
-extern void ReplicationSlotDropAcquired(void);
+extern void ReplicationSlotDropAcquired(bool disable_logical_decoding);
 extern void ReplicationSlotAlter(const char *name, const bool *failover,
 								 const bool *two_phase);
 
diff --git a/src/test/recovery/t/051_effective_wal_level.pl b/src/test/recovery/t/051_effective_wal_level.pl
index c4c2662f72b..663ed730c91 100644
--- a/src/test/recovery/t/051_effective_wal_level.pl
+++ b/src/test/recovery/t/051_effective_wal_level.pl
@@ -141,6 +141,20 @@ test_wal_level($primary, "replica|replica",
 	"effective_wal_level got decreased to 'replica' after invalidating the last logical slot"
 );
 
+# Logical decoding should be disabled after repacking
+$primary->safe_psql('postgres', qq[create table foo(a int primary key)]);
+$primary->safe_psql('postgres', qq[repack (concurrently) foo;]);
+ok( $primary->log_contains(
+		"logical decoding is enabled upon creating a new logical replication slot"
+	),
+	"logical decoding has been enabled upon creating a temp slot");
+
+# Wait for the checkpointer to disable logical decoding.
+wait_for_logical_decoding_disabled($primary);
+test_wal_level($primary, "replica|replica",
+	"effective_wal_level got decreased to 'replica' after the REPACK (CONCURRENTLY) command"
+);
+
 # Revert the modified settings, and restart the server.
 $primary->adjust_conf('postgresql.conf', 'max_slot_wal_keep_size', undef);
 $primary->adjust_conf('postgresql.conf', 'min_wal_size', undef);
-- 
2.49.0

