Subject: [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription

From: Bertrand Drouvot <[email-masked]>
Date: Fri, 3 Jul 2026 05:18:44 +0000

Similarly to what has been done for AlterSubscription() in XXXX, re-read the
subscription tuple after LockSharedObject() in DropSubscription().

A concurrent DROP or ALTER may have committed while we were waiting for the lock.
Without a re-read, DropSubscription would deal with invalid data, which currently
produces a confusing "tuple concurrently updated" elog() from CatalogTupleDelete().

Author: Bertrand Drouvot <[email-masked]>
Reviewed-by:
Reviewed-by:
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
 src/backend/commands/subscriptioncmds.c | 36 ++++++++++++++++++-------
 1 file changed, 27 insertions(+), 9 deletions(-)
 100.0% src/backend/commands/

diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index be03b3eb7e1..6db92a931b9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2582,17 +2582,8 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 		return;
 	}
 
-	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
-							Anum_pg_subscription_subconninfo, &isnull);
-	if (!isnull)
-		subconninfo = TextDatumGetCString(datum);
-
 	form = (Form_pg_subscription) GETSTRUCT(tup);
 	subid = form->oid;
-	subowner = form->subowner;
-	subserver = form->subserver;
-	subconflictlogrelid = form->subconflictlogrelid;
-	must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
 
 	/* must be owner */
 	if (!object_ownercheck(SubscriptionRelationId, subid, GetUserId()))
@@ -2608,6 +2599,33 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
 	 */
 	LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
 
+	/*
+	 * Re-read the subscription tuple after acquiring the lock. A concurrent
+	 * DROP or ALTER may have committed before we acquired the lock.
+	 */
+	ReleaseSysCache(tup);
+	tup = SearchSysCache2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
+						  CStringGetDatum(stmt->subname));
+
+	if (!HeapTupleIsValid(tup))
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("subscription \"%s\" does not exist",
+						stmt->subname)));
+
+	form = (Form_pg_subscription) GETSTRUCT(tup);
+	subowner = form->subowner;
+	subserver = form->subserver;
+	subconflictlogrelid = form->subconflictlogrelid;
+	must_use_password = !superuser_arg(subowner) && form->subpasswordrequired;
+
+	datum = SysCacheGetAttr(SUBSCRIPTIONOID, tup,
+							Anum_pg_subscription_subconninfo, &isnull);
+	if (!isnull)
+		subconninfo = TextDatumGetCString(datum);
+	else
+		subconninfo = NULL;
+
 	/* Get subname */
 	datum = SysCacheGetAttrNotNull(SUBSCRIPTIONOID, tup,
 								   Anum_pg_subscription_subname);
-- 
2.34.1

--IX1d9TnmxjGKMYcM--