Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wfGDH-005MTI-2L for pgsql-hackers@arkaria.postgresql.org; Thu, 02 Jul 2026 12:08:15 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wfGDF-001a8k-02 for pgsql-hackers@arkaria.postgresql.org; Thu, 02 Jul 2026 12:08:13 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wfGDE-001a8V-2E for pgsql-hackers@lists.postgresql.org; Thu, 02 Jul 2026 12:08:12 +0000 Received: from mail-wr1-x433.google.com ([2a00:1450:4864:20::433]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wfGDC-00000001MwH-1Hy4 for pgsql-hackers@lists.postgresql.org; Thu, 02 Jul 2026 12:08:12 +0000 Received: by mail-wr1-x433.google.com with SMTP id ffacd0b85a97d-45fd464d51fso854055f8f.3 for ; Thu, 02 Jul 2026 05:08:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782994089; x=1783598889; darn=lists.postgresql.org; h=content-disposition:mime-version:message-id:subject:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=YpLJEE+XGgkrq3KW65+S3pHj4bv8NkY67AuKkWYK0/s=; b=UDe7xCIjNcS0xKuNMOuzYkN2D23RzLUb9FX/ETe+2aLJ70+XcYRzgCETYFSFNnEWaM lj+AacVNJaS07OglvXX2ml1SOriUD7t71B3BtGuLRw+hoWn0BUD9Vr3+xAbjT0M1/rDa Wp1CLpELckJebhLFWWhDie0XeiRQA8YkjEDpL2f+Yvgk8Y1gexzewWutM0DV1I1bcAsP sA4+/X8pTwIK+4JK5vFbhqxp3fOAn9S8Hl6x9Up9ZtsTedWNqQUdpM1XBoyymlDZxizI k9S6gxxASFKS6MTCp0ApTSz1r3yJB4oxI6RQQGfDZsB9L/EIqdfCp11CITlpgaq+v6Wn al0w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782994089; x=1783598889; h=content-disposition:mime-version:message-id:subject:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=YpLJEE+XGgkrq3KW65+S3pHj4bv8NkY67AuKkWYK0/s=; b=O5SpyfgQWj0Gl0QW58e6IF2uKydcZD7PNMTzFiFDHL0IkBjSwVnFhMGNd7xK60vVro HArZgODDHv5W1e07Ia2/inX/KHaE690tgRu7I+AAKIYWLFN2KxDmNcTe0Ef4mjypGwt5 x1XOOJ+xyn6DSLtLhv8sCWwoZTAMkX4yCbwVM4yEVgt9aig8cOCNmJtaMeLQFxH3WZcM iThpcWwdGSPRJ8wmIYrQoziok3CDTG3MtK9rNTLQwqSAVGWazWr400/lgdYYrnvhC2eo QDWS+yIneTtkig3AQAXVJGKv/+I8bdNHJB2/EY4k6vMklybZPKfPVHwuwnIB3Y3yD6EH Bbfg== X-Gm-Message-State: AOJu0YybVGR6sAL417fF4cRGG3SnSIXWW4W1yXNKKJ/ay8X28kkkRCgH anWQ1+cGW7imdNGW0N9iDbQ9WKq8jk1kL986jrEdGoNMq93LHe+2kVjMDcnXmQ== X-Gm-Gg: AfdE7cmBi9zqmfVRRGIa34YTxllPGH2ysJPx7k5P9eBjMaAN8COSYdzZb4z9Q1V1kgS qdaMFRE+jm2Jutjl5Cvh9SdtFUTMQN10FNWwgIx26fx3Fo4b8YOgDuK+pWn5pj3Pum0zQ0NQ2w3 KpaTg8SC1KymuhHJmAgxm+zo7odj0EOXCDM1wB/c19kbJQ1811YhXmultEVobHbfOKgDtvcvJK1 Pit9rs7XJe8fR4BI0JH7nYNbj0kBHYdM6caY6l/pv1ctyI+9j0ylCL4YMv8HoQ/kJvSAIV3oSLF zW/JZkKhyxUVlzFKCQ0bbM4RJcECMAlvClJPggTaMlW1pFuBYLUZm4TlTu4zhp/EnVPPh9hnWUk UX9/LmQoo5SW1Dr491Zob3MmT08h52Xun14zXTOSsor+ZcnNSvDCfgBiyLYLVVsqsKrwnpZys12 POhnQW5UsLQCkgsz9bQM7lWTCRvh6/VIH3OPXxJ7U190aCQxT0PJEQk8+gdwgOZAbrKLCsBWw7 X-Received: by 2002:a05:6000:230f:b0:46e:98a7:232b with SMTP id ffacd0b85a97d-477573bbc16mr8761047f8f.10.1782994088810; Thu, 02 Jul 2026 05:08:08 -0700 (PDT) Received: from bdtpg (ec2-15-237-197-144.eu-west-3.compute.amazonaws.com. [15.237.197.144]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbc75sm8952679f8f.5.2026.07.02.05.08.08 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 05:08:08 -0700 (PDT) Date: Thu, 2 Jul 2026 12:08:06 +0000 From: Bertrand Drouvot To: pgsql-hackers@lists.postgresql.org Subject: Re-read subscription state after lock in AlterSubscription Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="MHKy70OBPdw7Q14M" Content-Disposition: inline List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --MHKy70OBPdw7Q14M Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi hackers, while playing with the new ALTER SUBSCRIPTION parameter added in a5918fddf10, I realized that the subscription is not re-read once we acquire the lock in AlterSubscription(). This pre-existing issue is now more visible after a5918fddf10: 1/ two concurrent ALTER SUBSCRIPTION SET (conflict_log_destination = 'table') could result in the second session attempting to create an already-existing conflict log table, producing a confusing "relation already exists" error: ERROR: relation "pg_conflict_log_24614" already exists It's confusing because ALTER SUBSCRIPTION SET (conflict_log_destination = 'table') would not report an error if the conflict table already exists (and no concurrent ALTER is running). 2/ a concurrent DROP followed by the ALTER would emit a NOTICE about creating the conflict log table before failing with "referenced subscription was concurrently dropped". That sounds like a weird messaging: NOTICE: created conflict log table "pg_conflict.pg_conflict_log_24620" for subscription "mysub" ERROR: referenced subscription was concurrently dropped The attached fixes it by: - Re-reading the subscription tuple after LockSharedObject() and refreshing the Subscription struct. - Moving the local variable assignments to after the re-read. - Re-checking the password_required privilege restriction after the re-read. Remarks: 1/ not re-checking password_required after the re-read would still produce a "tuple concurrently updated" error, but re-checking it allows us to display a better error message. 2/ the ownership check is intentionally not re-done after the lock because AlterSubscriptionOwner() does not take AccessExclusiveLock on the subscription object: it only takes RowExclusiveLock on the pg_subscription catalog table. This means ownership can change regardless of our lock, making a re-check after lock acquisition pointless. The existing "tuple concurrently updated" error from CatalogTupleUpdate() already provides a protection if ownership changes concurrently. 3/ the "privileges" checks are still also done before the lock acquisition because we don't want to lock an object we don't have privileges on. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com --MHKy70OBPdw7Q14M Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0001-Re-read-subscription-state-after-lock-in-AlterSub.patch" From 72a53d1991e7cd9d4a52f48284ddc974a0a4ae65 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Thu, 2 Jul 2026 11:07:04 +0000 Subject: [PATCH v1] Re-read subscription state after lock in AlterSubscription AlterSubscription() reads the subscription's catalog state via GetSubscription() before acquiring AccessExclusiveLock on the subscription object. A concurrent session that commits a DROP or ALTER between the read and the lock acquisition leaves the other session acting with stale information once it unblocks. Fix by: - Re-reading the subscription tuple after LockSharedObject() and refreshing the Subscription struct. - Moving the local variable assignments to after the re-read. - Re-checking the password_required privilege restriction after the re-read. Remarks: 1/ not re-checking password_required after the re-read would still produce a "tuple concurrently updated" error, but re-checking it allows us to display a better error message. 2/ the ownership check is intentionally not re-done after the lock because AlterSubscriptionOwner() does not take AccessExclusiveLock on the subscription object: it only takes RowExclusiveLock on the pg_subscription catalog table. This means ownership can change regardless of our lock, making a re-check after lock acquisition pointless. The existing "tuple concurrently updated" error from CatalogTupleUpdate() already provides a protection if ownership changes concurrently. Author: Bertrand Drouvot Reviewed-by: Discussion: --- src/backend/commands/subscriptioncmds.c | 41 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) 100.0% src/backend/commands/ diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c index 4292e7fb8f4..be03b3eb7e1 100644 --- a/src/backend/commands/subscriptioncmds.c +++ b/src/backend/commands/subscriptioncmds.c @@ -1695,11 +1695,6 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt, */ sub = GetSubscription(subid, false, orig_conninfo_needed, false); - retain_dead_tuples = sub->retaindeadtuples; - origin = sub->origin; - max_retention = sub->maxretention; - retention_active = sub->retentionactive; - /* * Don't allow non-superuser modification of a subscription with * password_required=false. @@ -1713,6 +1708,42 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt, /* Lock the subscription so nobody else can do anything with it. */ 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. + */ + heap_freetuple(tup); + tup = SearchSysCacheCopy2(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); + + /* Refresh the subscription. */ + pfree(sub); + sub = GetSubscription(subid, false, orig_conninfo_needed, false); + + /* + * Re-check whether a non-superuser is allowed to alter this subscription. + * A concurrent ALTER may have set password_required=false while we were + * waiting for the lock. + */ + if (!sub->passwordrequired && !superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("password_required=false is superuser-only"), + errhint("Subscriptions with the password_required option set to false may only be created or modified by the superuser."))); + + retain_dead_tuples = sub->retaindeadtuples; + origin = sub->origin; + max_retention = sub->maxretention; + retention_active = sub->retentionactive; + /* Form a new tuple. */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); -- 2.34.1 --MHKy70OBPdw7Q14M--