agora inbox for [email protected]
help / color / mirror / Atom feed[PATCH v34 11/11] Add documentations about Incremental View Maintenance
701+ messages / 6 participants
[nested] [flat]
* [PATCH v34 11/11] Add documentations about Incremental View Maintenance
@ 2019-12-20 01:25 Yugo Nagata <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Yugo Nagata @ 2019-12-20 01:25 UTC (permalink / raw)
---
doc/src/sgml/catalogs.sgml | 9 +
.../sgml/ref/create_materialized_view.sgml | 124 ++++-
.../sgml/ref/refresh_materialized_view.sgml | 8 +-
doc/src/sgml/rules.sgml | 437 ++++++++++++++++++
doc/src/sgml/system-views.sgml | 9 +
5 files changed, 583 insertions(+), 4 deletions(-)
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b654fae1b2..8ef73edd12 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -2231,6 +2231,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>relisivm</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if relation is incrementally maintainable materialized view
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>relrewrite</structfield> <type>oid</type>
diff --git a/doc/src/sgml/ref/create_materialized_view.sgml b/doc/src/sgml/ref/create_materialized_view.sgml
index 0d2fea2b97..8c574062db 100644
--- a/doc/src/sgml/ref/create_materialized_view.sgml
+++ b/doc/src/sgml/ref/create_materialized_view.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
+CREATE [ INCREMENTAL ] MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
[ (<replaceable>column_name</replaceable> [, ...] ) ]
[ USING <replaceable class="parameter">method</replaceable> ]
[ WITH ( <replaceable class="parameter">storage_parameter</replaceable> [= <replaceable class="parameter">value</replaceable>] [, ... ] ) ]
@@ -60,6 +60,125 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
<title>Parameters</title>
<variablelist>
+ <varlistentry>
+ <term><literal>INCREMENTAL</literal></term>
+ <listitem>
+ <para>
+ If specified, some triggers are automatically created so that the rows
+ of the materialized view are immediately updated when base tables of the
+ materialized view are updated. In general, this allows faster update of
+ the materialized view at a price of slower update of the base tables
+ because the triggers will be invoked. We call this form of materialized
+ view as "Incrementally Maintainable Materialized View" (IMMV).
+ </para>
+ <para>
+ When <acronym>IMMV</acronym> is defined without using <command>WITH NO DATA</command>,
+ a unique index is created on the view automatically if possible. If the view
+ definition query has a GROUP BY clause, a unique index is created on the columns
+ of GROUP BY expressions. Also, if the view has DISTINCT clause, a unique index
+ is created on all columns in the target list. Otherwise, if the view contains all
+ primary key attritubes of its base tables in the target list, a unique index is
+ created on these attritubes. In other cases, no index is created.
+ </para>
+ <para>
+ There are restrictions of query definitions allowed to use this
+ option. The following are supported in query definitions for IMMV:
+ <itemizedlist>
+
+ <listitem>
+ <para>
+ Inner joins (including self-joins).
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Some built-in aggregate functions (count, sum, avg, min, max) without a HAVING
+ clause.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ Unsupported queries with this option include the following:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Outer joins.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Sub-queries.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Aggregate functions other than built-in count, sum, avg, min and max.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Aggregate functions with a HAVING clause.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ DISTINCT ON, WINDOW, VALUES, LIMIT and OFFSET clause.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ Other restrictions include:
+ <itemizedlist>
+
+ <listitem>
+ <para>
+ IMMVs must be based on simple base tables. It's not supported to
+ create them on top of views or materialized views.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ It is not supported to include system columns in an IMMV.
+ <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm02 AS SELECT i,j FROM mv_base_a WHERE xmin = '610';
+ERROR: system column is not supported with IVM
+ </programlisting>
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Non-immutable functions are not supported.
+ <programlisting>
+CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm12 AS SELECT i,j FROM mv_base_a WHERE i = random()::int;
+ERROR: functions in IMMV must be marked IMMUTABLE
+ </programlisting>
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ IMMVs do not support expressions that contains aggregates
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Logical replication does not support IMMVs.
+ </para>
+ </listitem>
+
+ </itemizedlist>
+
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><literal>IF NOT EXISTS</literal></term>
<listitem>
@@ -155,7 +274,8 @@ CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] <replaceable>table_name</replaceable>
This clause specifies whether or not the materialized view should be
populated at creation time. If not, the materialized view will be
flagged as unscannable and cannot be queried until <command>REFRESH
- MATERIALIZED VIEW</command> is used.
+ MATERIALIZED VIEW</command> is used. Also, if the view is IMMV,
+ triggers for maintaining the view are not created.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/ref/refresh_materialized_view.sgml b/doc/src/sgml/ref/refresh_materialized_view.sgml
index 8ed43ade80..a4d729bdf0 100644
--- a/doc/src/sgml/ref/refresh_materialized_view.sgml
+++ b/doc/src/sgml/ref/refresh_materialized_view.sgml
@@ -36,9 +36,13 @@ REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] <replaceable class="parameter">name</
privilege on the materialized view. The old contents are discarded. If
<literal>WITH DATA</literal> is specified (or defaults) the backing query
is executed to provide the new data, and the materialized view is left in a
- scannable state. If <literal>WITH NO DATA</literal> is specified no new
+ scannable state. If the view is an incrementally maintainable materialized
+ view (IMMV) and was unpopulated, triggers for maintaining the view are
+ created. Also, a unique index is created for IMMV if it is possible and the
+ view doesn't have that yet.
+ If <literal>WITH NO DATA</literal> is specified no new
data is generated and the materialized view is left in an unscannable
- state.
+ state. If the view is IMMV, the triggers are dropped.
</para>
<para>
<literal>CONCURRENTLY</literal> and <literal>WITH NO DATA</literal> may not
diff --git a/doc/src/sgml/rules.sgml b/doc/src/sgml/rules.sgml
index 7a928bd7b9..73597ea7a5 100644
--- a/doc/src/sgml/rules.sgml
+++ b/doc/src/sgml/rules.sgml
@@ -1100,6 +1100,443 @@ SELECT word FROM words ORDER BY word <-> 'caterpiler' LIMIT 10;
</sect1>
+<sect1 id="rules-ivm">
+<title>Incremental View Maintenance</title>
+
+<indexterm zone="rules-ivm">
+ <primary>incremental view maintenance</primary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>materialized view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<indexterm zone="rules-ivm">
+ <primary>view</primary>
+ <secondary>incremental view maintenance</secondary>
+</indexterm>
+
+<sect2 id="rules-ivm-overview">
+<title>Overview</title>
+
+<para>
+ Incremental View Maintenance (<acronym>IVM</acronym>) is a way to make
+ materialized views up-to-date in which only incremental changes are computed
+ and applied on views rather than recomputing the contents from scratch as
+ <command>REFRESH MATERIALIZED VIEW</command> does. <acronym>IVM</acronym>
+ can update materialized views more efficiently than recomputation when only
+ small parts of the view are changed.
+</para>
+
+<para>
+ There are two approaches with regard to timing of view maintenance:
+ immediate and deferred. In immediate maintenance, views are updated in the
+ same transaction that its base table is modified. In deferred maintenance,
+ views are updated after the transaction is committed, for example, when the
+ view is accessed, as a response to user command like <command>REFRESH
+ MATERIALIZED VIEW</command>, or periodically in background, and so on.
+ <productname>PostgreSQL</productname> currently implements only a kind of
+ immediate maintenance, in which materialized views are updated immediately
+ in AFTER triggers when a base table is modified.
+</para>
+
+<para>
+ To create materialized views supporting <acronym>IVM</acronym>, use the
+ <command>CREATE INCREMENTAL MATERIALIZED VIEW</command>, for example:
+<programlisting>
+CREATE <emphasis>INCREMENTAL</emphasis> MATERIALIZED VIEW mymatview AS SELECT * FROM mytab;
+</programlisting>
+ When a materialized view is created with the <literal>INCREMENTAL</literal>
+ keyword, some triggers are automatically created so that the view's contents are
+ immediately updated when its base tables are modified. We call this form
+ of materialized view an Incrementally Maintainable Materialized View
+ (<acronym>IMMV</acronym>).
+<programlisting>
+postgres=# CREATE INCREMENTAL MATERIALIZED VIEW m AS SELECT * FROM t0;
+NOTICE: could not create an index on materialized view "m" automatically
+HINT: Create an index on the materialized view for effcient incremental maintenance.
+SELECT 3
+postgres=# SELECT * FROM m;
+ i
+---
+ 1
+ 2
+ 3
+(3 rows)
+
+postgres=# INSERT INTO t0 VALUES (4);
+INSERT 0 1
+postgres=# SELECT * FROM m; -- automatically updated
+ i
+---
+ 1
+ 2
+ 3
+ 4
+(4 rows)
+</programlisting>
+</para>
+
+<para>
+ Some <acronym>IMMV</acronym>s have hidden columns which are added
+ automatically when a materialized view is created. Their name starts
+ with <literal>__ivm_</literal> and they contain information required
+ for maintaining the <acronym>IMMV</acronym>. Such columns are not visible
+ when the <acronym>IMMV</acronym> is accessed by <literal>SELECT *</literal>
+ but are visible if the column name is explicitly specified in the target
+ list. We can also see the hidden columns in <literal>\d</literal>
+ meta-commands of <command>psql</command> commands.
+</para>
+
+<para>
+ In general, <acronym>IMMV</acronym>s allow faster updates of materialized
+ views at the price of slower updates to their base tables. Updates of
+ <acronym>IMMV</acronym> is slower because triggers will be invoked and the
+ view is updated in triggers per modification statement.
+</para>
+
+<para>
+ For example, suppose a normal materialized view defined as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+ SELECT a.aid, b.bid, a.abalance, b.bbalance
+ FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+SELECT 10000000
+
+</programlisting>
+
+ Updating a tuple in a base table of this materialized view is rapid but the
+ <command>REFRESH MATERIALIZED VIEW</command> command on this view takes a long time:
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 0.990 ms
+
+test=# REFRESH MATERIALIZED VIEW mv_normal ;
+REFRESH MATERIALIZED VIEW
+Time: 33533.952 ms (00:33.534)
+</programlisting>
+</para>
+
+<para>
+ On the other hand, after creating <acronym>IMMV</acronym> with the same view
+ definition as below:
+
+<programlisting>
+test=# CREATE INCREMENTAL MATERIALIZED VIEW mv_ivm AS
+ SELECT a.aid, b.bid, a.abalance, b.bbalance
+ FROM pgbench_accounts a JOIN pgbench_branches b USING(bid);
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+NOTICE: created index "mv_ivm_index" on materialized view "mv_ivm"
+</programlisting>
+
+ updating a tuple in a base table takes more than the normal view,
+ but its content is updated automatically and this is faster than the
+ <command>REFRESH MATERIALIZED VIEW</command> command.
+
+<programlisting>
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 13.068 ms
+</programlisting>
+
+</para>
+
+<para>
+ Appropriate indexes on <acronym>IMMV</acronym>s are necessary for
+ efficient <acronym>IVM</acronym> because it looks for tuples to be
+ updated in <acronym>IMMV</acronym>. If there are no indexes, it
+ will take a long time.
+</para>
+
+<para>
+ Therefore, when <acronym>IMMV</acronym> is defined, a unique index is created on the view
+ automatically if possible. If the view definition query has a GROUP BY clause, a unique
+ index is created on the columns of GROUP BY expressions. Also, if the view has DISTINCT
+ clause, a unique index is created on all columns in the target list. Otherwise, if the
+ view contains all primary key attritubes of its base tables in the target list, a unique
+ index is created on these attritubes. In other cases, no index is created.
+</para>
+
+<para>
+ In the previous example, a unique index "mv_ivm_index" is created on aid and bid
+ columns of materialized view "mv_ivm", and this enables the rapid update of the view.
+ Dropping this index make updating the view take a loger time.
+<programlisting>
+test=# DROP INDEX mv_ivm_index;
+DROP INDEX
+Time: 67.081 ms
+
+test=# UPDATE pgbench_accounts SET abalance = 1000 WHERE aid = 1;
+UPDATE 1
+Time: 16386.245 ms (00:16.386)
+</programlisting>
+
+</para>
+
+<para>
+ <acronym>IVM</acronym> is effective when we want to keep a materialized
+ view up-to-date and small fraction of a base table is modified
+ infrequently. Due to the overhead of immediate maintenance, <acronym>IVM</acronym>
+ is not effective when a base table is modified frequently. Also, when a
+ large part of a base table is modified or large data is inserted into a
+ base table, <acronym>IVM</acronym> is not effective and the cost of
+ maintenance can be larger than the <command>REFRESH MATERIALIZED VIEW</command>
+ command. In such situation, we can use <command>REFRESH MATERIALIZED VIEW</command>
+ and specify <literal>WITH NO DATA</literal> to disable immediate
+ maintenance before modifying a base table. After a base table modification,
+ execute the <command>REFRESH MATERIALIZED VIEW</command> (with <literal>WITH DATA</literal>)
+ command to refresh the view data and enable immediate maintenance.
+</para>
+
+</sect2>
+
+<sect2 id="rules-ivm-support">
+<title>Supported View Definitions and Restrictions</title>
+
+<para>
+ Currently, we can create <acronym>IMMV</acronym>s using inner joins, and some
+ aggregates. However, several restrictions apply to the definition of IMMV.
+</para>
+
+<sect3 id="rules-ivm-support-joins">
+<title>Joins</title>
+<para>
+ Inner joins including self-join are supported. Outer joins are not supported.
+</para>
+</sect3>
+
+<sect3 id="rules-ivm-support-aggregates">
+<title>Aggregates</title>
+<para>
+ Supported aggregate functions are <function>count</function>, <function>sum</function>,
+ <function>avg</function>, <function>min</function>, and <function>max</function>.
+ Currently, only built-in aggregate functions are supported and user defined
+ aggregates cannot be used. When a base table is modified, the new aggregated
+ values are incrementally calculated using the old aggregated values and values
+ of related hidden columns stored in <acronym>IMMV</acronym>.
+</para>
+
+<para>
+ Note that for <function>min</function> or <function>max</function>, the new values
+ could be re-calculated from base tables with regard to the affected groups when a
+ tuple containing the current minimal or maximal values are deleted from a base table.
+ Therefore, it can takes a long time to update an <acronym>IMMV</acronym> containing
+ these functions.
+</para>
+
+<para>
+ Also note that using <function>sum</function> or <function>avg</function> on
+ <type>real</type> (<type>float4</type>) type or <type>double precision</type>
+ (<type>float8</type>) type in <acronym>IMMV</acronym> is unsafe. This is
+ because aggregated values in <acronym>IMMV</acronym> can become different from
+ results calculated from base tables due to the limited precision of these types.
+ To avoid this problem, use the <type>numeric</type> type instead.
+</para>
+
+ <sect4 id="rules-ivm-restrictions-aggregates">
+ <title>Restrictions on Aggregates</title>
+ <para>
+ There are the following restrictions:
+ <itemizedlist>
+ <listitem>
+ <para>
+ If we have a <literal>GROUP BY</literal> clause, expressions specified in
+ <literal>GROUP BY</literal> must appear in the target list. This is
+ how tuples to be updated in the <acronym>IMMV</acronym> are identified.
+ These attributes are used as scan keys for searching tuples in the
+ <acronym>IMMV</acronym>, so indexes on them are required for efficient
+ <acronym>IVM</acronym>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <literal>HAVING</literal> clause cannot be used.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+ </sect4>
+</sect3>
+
+<sect3 id="rules-ivm-general-restricitons">
+<title>Other General Restrictions</title>
+<para>
+ There are other restrictions which generally apply to <acronym>IMMV</acronym>:
+ <itemizedlist>
+ <listitem>
+ <para>
+ Sub-queries cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ CTEs cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Window functions cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <acronym>IMMV</acronym>s must be based on simple base tables. It's not
+ supported to create them on top of views, materialized views, foreign tables, inhe.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ LIMIT and OFFSET clauses cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <acronym>IMMV</acronym>s cannot contain system columns.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <acronym>IMMV</acronym>s cannot contain non-immutable functions.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ UNION/INTERSECT/EXCEPT clauses cannnot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ DISTINCT ON clauses cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ TABLESAMPLE parameter cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ inheritance parent tables cannnot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ VALUES clause cannnot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ <literal>GROUPING SETS</literal> and <literal>FILTER</literal> clauses cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ FOR UPDATE/SHARE cannot be used.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ targetlist cannot contain columns whose name start with <literal>__ivm_</literal>.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ targetlist cannot contain expressions which contain an aggregate in it.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Logical replication is not supported, that is, even when a base table
+ at a publisher node is modified, <acronym>IMMV</acronym>s at subscriber
+ nodes are not updated.
+ </para>
+ </listitem>
+
+ </itemizedlist>
+</para>
+</sect3>
+
+</sect2>
+
+<sect2 id="rules-ivm-distinct">
+<title><literal>DISTINCT</literal></title>
+
+<para>
+ <productname>PostgreSQL</productname> supports <acronym>IMMV</acronym> with
+ <literal>DISTINCT</literal>. For example, suppose a <acronym>IMMV</acronym>
+ defined with <literal>DISTINCT</literal> on a base table containing duplicate
+ tuples. When tuples are deleted from the base table, a tuple in the view is
+ deleted if and only if the multiplicity of the tuple becomes zero. Moreover,
+ when tuples are inserted into the base table, a tuple is inserted into the
+ view only if the same tuple doesn't already exist in it.
+</para>
+
+<para>
+ Physically, an <acronym>IMMV</acronym> defined with <literal>DISTINCT</literal>
+ contains tuples after eliminating duplicates, and the multiplicity of each tuple
+ is stored in a hidden column named <literal>__ivm_count__</literal>.
+</para>
+</sect2>
+
+<sect2 id="rules-ivm-concurrent-transactions">
+<title>Concurrent Transactions</title>
+<para>
+ Suppose an <acronym>IMMV</acronym> is defined on two base tables and each
+ table was modified in different a concurrent transaction simultaneously.
+ In the transaction which was committed first, <acronym>IMMV</acronym> can
+ be updated considering only the change which happened in this transaction.
+ On the other hand, in order to update the view correctly in the transaction
+ which was committed later, we need to know the changes occurred in
+ both transactions. For this reason, <literal>ExclusiveLock</literal>
+ is held on an <acronym>IMMV</acronym> immediately after a base table is
+ modified in <literal>READ COMMITTED</literal> mode to make sure that
+ the <acronym>IMMV</acronym> is updated in the latter transaction after
+ the former transaction is committed. In <literal>REPEATABLE READ</literal>
+ or <literal>SERIALIZABLE</literal> mode, an error is raised immediately
+ if lock acquisition fails because any changes which occurred in
+ other transactions are not be visible in these modes and
+ <acronym>IMMV</acronym> cannot be updated correctly in such situations.
+ However, as an exception if the view has only one base table and
+ <command>INSERT</command> is performed on the table,
+ the lock held on thew view is <literal>RowExclusiveLock</literal>.
+</para>
+</sect2>
+
+<sect2 id="rules-ivm-rls">
+<title>Row Level Security</title>
+<para>
+ If some base tables have row level security policy, rows that are not visible
+ to the materialized view's owner are excluded from the result. In addition, such
+ rows are excluded as well when views are incrementally maintained. However, if a
+ new policy is defined or policies are changed after the materialized view was created,
+ the new policy will not be applied to the view contents. To apply the new policy,
+ you need to refresh materialized views.
+</para>
+</sect2>
+
+</sect1>
+
<sect1 id="rules-update">
<title>Rules on <command>INSERT</command>, <command>UPDATE</command>, and <command>DELETE</command></title>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index bdc34cf94e..d4a1c99a91 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -1796,6 +1796,15 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
</para></entry>
</row>
+ <row>
+ <entry role="catalog_table_entry"><para role="column_definition">
+ <structfield>isimmv</structfield> <type>bool</type>
+ </para>
+ <para>
+ True if materialized view is incrementally maintainable
+ </para></entry>
+ </row>
+
<row>
<entry role="catalog_table_entry"><para role="column_definition">
<structfield>definition</structfield> <type>text</type>
--
2.34.1
--Multipart=_Thu__11_Jul_2024_13_23_57_+0900_hKsU2G3a3BgS2FFs
Content-Type: text/x-diff;
name="v34-0010-Add-regression-tests-for-Incremental-View-Mainte.patch"
Content-Disposition: attachment;
filename="v34-0010-Add-regression-tests-for-Incremental-View-Mainte.patch"
Content-Transfer-Encoding: 7bit
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v1] Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 11:07 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 11:07 UTC (permalink / raw)
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 <[email protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 12:08 Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 12:08 UTC (permalink / raw)
To: [email protected]
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
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 12:27 Dilip Kumar <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Dilip Kumar @ 2026-07-02 12:27 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: [email protected]
On Thu, Jul 2, 2026 at 5:38 PM Bertrand Drouvot
<[email protected]> wrote:
>
> 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.
>
Thanks Bertrand, yeah this seems like a valid issue, and I agree we
need to reread the subscription after acquiring the object lock.
--
Regards,
Dilip Kumar
Google
^ permalink raw reply [nested|flat] 701+ messages in thread
* RE: Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 12:48 Hayato Kuroda (Fujitsu) <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-02 12:48 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: [email protected] <[email protected]>; 'Dilip Kumar' <[email protected]>
Dear Bertrand,
Good catch. Current code allows that old `sub` value is retained, so it sounds
reasonable fix even for me.
BTW, the issue that GetSubscription() is called before the LockSharedObject() looks
the existing issues even on REL_13_STABLE. So does it mean that there were no
cases that concurrent altering can be the unexpected state? At least,
"retain_dead_tuples" can avoid the issue because the launcher manages the
conflict slot.
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-02 13:20 Bertrand Drouvot <[email protected]>
parent: Hayato Kuroda (Fujitsu) <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-02 13:20 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <[email protected]>; +Cc: [email protected] <[email protected]>; 'Dilip Kumar' <[email protected]>
Hi Kuroda-san,
On Thu, Jul 02, 2026 at 12:48:53PM +0000, Hayato Kuroda (Fujitsu) wrote:
> Dear Bertrand,
>
> Good catch. Current code allows that old `sub` value is retained, so it sounds
> reasonable fix even for me.
>
> BTW, the issue that GetSubscription() is called before the LockSharedObject() looks
> the existing issues even on REL_13_STABLE. So does it mean that there were no
> cases that concurrent altering can be the unexpected state?
Yeah, but I think they would produce "tuple concurrently updated" error (due to
CatalogTupleUpdate) so that invalid information could not be used.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* RE: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 03:13 Hayato Kuroda (Fujitsu) <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-03 03:13 UTC (permalink / raw)
To: 'Bertrand Drouvot' <[email protected]>; +Cc: [email protected] <[email protected]>; 'Dilip Kumar' <[email protected]>
Dear Bertrand,
> Yeah, but I think they would produce "tuple concurrently updated" error (due to
> CatalogTupleUpdate) so that invalid information could not be used.
I confirmed with PG14 that tuple concurrently updated ERROR can be raised when
ALTER SUBSCRIPTION DISABLE happens concurrently:
```
postgres=# ALTER SUBSCRIPTION sub DISABLE ;
ERROR: tuple concurrently updated
```
It might be harmless but I think the correct ERROR should be reported: the patch
should be backpatched. Thought?
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 04:19 Bertrand Drouvot <[email protected]>
parent: Hayato Kuroda (Fujitsu) <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 04:19 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <[email protected]>; +Cc: [email protected] <[email protected]>; 'Dilip Kumar' <[email protected]>
Hi Kuroda-san,
On Fri, Jul 03, 2026 at 03:13:08AM +0000, Hayato Kuroda (Fujitsu) wrote:
> Dear Bertrand,
>
> > Yeah, but I think they would produce "tuple concurrently updated" error (due to
> > CatalogTupleUpdate) so that invalid information could not be used.
>
> I confirmed with PG14 that tuple concurrently updated ERROR can be raised when
> ALTER SUBSCRIPTION DISABLE happens concurrently:
>
> ```
> postgres=# ALTER SUBSCRIPTION sub DISABLE ;
> ERROR: tuple concurrently updated
> ```
Yeah, reproducible by using a breakpoint just before acquiring the lock for example.
> It might be harmless but I think the correct ERROR should be reported: the patch
> should be backpatched. Thought?
I'm not sure about the back patch part as it would only improve error messages
in a rare race condition (and there is no risk of invalid data being used).
Since a5918fddf10, that's a different story because a table creation is now
involved.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 04:50 Dilip Kumar <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Dilip Kumar @ 2026-07-03 04:50 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Fri, Jul 3, 2026 at 9:49 AM Bertrand Drouvot
<[email protected]> wrote:
>
> Hi Kuroda-san,
>
> On Fri, Jul 03, 2026 at 03:13:08AM +0000, Hayato Kuroda (Fujitsu) wrote:
> > Dear Bertrand,
> >
> > > Yeah, but I think they would produce "tuple concurrently updated" error (due to
> > > CatalogTupleUpdate) so that invalid information could not be used.
> >
> > I confirmed with PG14 that tuple concurrently updated ERROR can be raised when
> > ALTER SUBSCRIPTION DISABLE happens concurrently:
> >
> > ```
> > postgres=# ALTER SUBSCRIPTION sub DISABLE ;
> > ERROR: tuple concurrently updated
> > ```
>
> Yeah, reproducible by using a breakpoint just before acquiring the lock for example.
>
> > It might be harmless but I think the correct ERROR should be reported: the patch
> > should be backpatched. Thought?
>
> I'm not sure about the back patch part as it would only improve error messages
> in a rare race condition (and there is no risk of invalid data being used).
Patch LGTM. IMHO we can backpatch this as it is a small change and
also fixes the bug, without this fix a non-superuser executing ALTER
SUBSCRIPTION could bypass the password_required=false restriction if a
concurrent transaction
updated that flag. However, we could argue that this is a corner case
and can be skipped but given the patch's simplicity, I recommend
backpatching.
--
Regards,
Dilip Kumar
Google
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:17 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:17 UTC (permalink / raw)
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
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
--IX1d9TnmxjGKMYcM
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v2-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 05:18 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:18 UTC (permalink / raw)
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 protected]>
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--
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 05:52 Bertrand Drouvot <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 05:52 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Fri, Jul 03, 2026 at 10:20:32AM +0530, Dilip Kumar wrote:
> On Fri, Jul 3, 2026 at 9:49 AM Bertrand Drouvot
> <[email protected]> wrote:
> >
> > Hi Kuroda-san,
> >
> > On Fri, Jul 03, 2026 at 03:13:08AM +0000, Hayato Kuroda (Fujitsu) wrote:
> > > Dear Bertrand,
> > >
> > > > Yeah, but I think they would produce "tuple concurrently updated" error (due to
> > > > CatalogTupleUpdate) so that invalid information could not be used.
> > >
> > > I confirmed with PG14 that tuple concurrently updated ERROR can be raised when
> > > ALTER SUBSCRIPTION DISABLE happens concurrently:
> > >
> > > ```
> > > postgres=# ALTER SUBSCRIPTION sub DISABLE ;
> > > ERROR: tuple concurrently updated
> > > ```
> >
> > Yeah, reproducible by using a breakpoint just before acquiring the lock for example.
> >
> > > It might be harmless but I think the correct ERROR should be reported: the patch
> > > should be backpatched. Thought?
> >
> > I'm not sure about the back patch part as it would only improve error messages
> > in a rare race condition (and there is no risk of invalid data being used).
>
> Patch LGTM.
Thanks for looking at it!
> IMHO we can backpatch this as it is a small change and
> also fixes the bug, without this fix a non-superuser executing ALTER
> SUBSCRIPTION could bypass the password_required=false restriction if a
> concurrent transaction
> updated that flag.
I don't think that's right. I just tested it with a breakpoint that way:
ALTER SUBSCRIPTION mysub SET (password_required = true);
ALTER SUBSCRIPTION mysub OWNER TO nonsuperuser;
gdb breakpoint at subscriptioncmds.c:1714 on session 1 (nonsuperuser)
session 1 (as nonsuperuser): start ALTER SUBSCRIPTION mysub SET (binary = true);
session 1 is paused by the breakpoint
session 2 (as superuser): ALTER SUBSCRIPTION mysub SET (password_required = false);
continue session 1, gives:
postgres=> ALTER SUBSCRIPTION mysub SET (binary = true);
ERROR: tuple concurrently updated
So it's also "protected" by this error.
> but given the patch's simplicity, I recommend
> backpatching.
That's right but that would only improve error messages. That said, looking closer,
they are elog() ones, so "not expected" to occur so yeah backpatch does make sense.
That said, what about also fixing DropSubscription() like in the 0002 attached?
(that would also produce those elog() messages in case of concurrent DROP or ALTER).
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* RE: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 08:08 Zhijie Hou (Fujitsu) <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 2 replies; 701+ messages in thread
From: Zhijie Hou (Fujitsu) @ 2026-07-03 08:08 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; Dilip Kumar <[email protected]>; +Cc: Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Friday, July 3, 2026 1:53 PM Bertrand Drouvot <[email protected]> wrote:
>
> > but given the patch's simplicity, I recommend backpatching.
>
> That's right but that would only improve error messages. That said, looking
> closer, they are elog() ones, so "not expected" to occur so yeah backpatch
> does make sense.
+1 for backpatching, even if it's rare, the "ERROR: tuple concurrently updated"
message seems confusing to me.
>
> That said, what about also fixing DropSubscription() like in the 0002 attached?
> (that would also produce those elog() messages in case of concurrent DROP or
> ALTER).
For the patch, I'm not sure if we must repeat the checks twice. Could we
simply move the original checks to after we take the lock? At least, the
GetSubscription() call and the password check can be moved there and old codes
can be deleted.
BTW, this may not be strictly related, but I think it's not safe to do the
ownership check before locking the subscription as well. If the subscription is
concurrently dropped, a "tuple concurrently updated" error can still occur.
(Thanks to Kuroda-San for discussing this with me off-list.)
Best Regards,
Hou zj
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 09:03 Bertrand Drouvot <[email protected]>
parent: Zhijie Hou (Fujitsu) <[email protected]>
1 sibling, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 09:03 UTC (permalink / raw)
To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Fri, Jul 03, 2026 at 08:08:13AM +0000, Zhijie Hou (Fujitsu) wrote:
> On Friday, July 3, 2026 1:53 PM Bertrand Drouvot <[email protected]> wrote:
> >
> > That said, what about also fixing DropSubscription() like in the 0002 attached?
> > (that would also produce those elog() messages in case of concurrent DROP or
> > ALTER).
>
> For the patch, I'm not sure if we must repeat the checks twice.
Thanks for looking at it!
> Could we
> simply move the original checks to after we take the lock? At least, the
> GetSubscription() call and the password check can be moved there and old codes
> can be deleted.
I'm not sure which checks you refer to. The ones that are keep before the lock
acquisition are because we don't want to lock an object we don't have privileges
on (see remark 3 in [1]).
> BTW, this may not be strictly related, but I think it's not safe to do the
> ownership check before locking the subscription as well. If the subscription is
> concurrently dropped, a "tuple concurrently updated" error can still occur.
That's right, I explained why in remark number 2 in [1]:
"
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.
"
Does that make sense?
[1]: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* RE: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 09:56 Zhijie Hou (Fujitsu) <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Zhijie Hou (Fujitsu) @ 2026-07-03 09:56 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Friday, July 3, 2026 5:03 PM Bertrand Drouvot <[email protected]> wrote:
> On Fri, Jul 03, 2026 at 08:08:13AM +0000, Zhijie Hou (Fujitsu) wrote:
> > On Friday, July 3, 2026 1:53 PM Bertrand Drouvot
> <[email protected]> wrote:
> > >
> > > That said, what about also fixing DropSubscription() like in the 0002
> attached?
> > > (that would also produce those elog() messages in case of concurrent
> > > DROP or ALTER).
> >
> > For the patch, I'm not sure if we must repeat the checks twice.
>
> Thanks for looking at it!
>
> > Could we
> > simply move the original checks to after we take the lock? At least,
> > the
> > GetSubscription() call and the password check can be moved there and
> > old codes can be deleted.
>
> I'm not sure which checks you refer to. The ones that are keep before the lock
> acquisition are because we don't want to lock an object we don't have
> privileges on (see remark 3 in [1]).
I was referring to the password_required check and the GetSubscription() call.
I think failing the password_required check does not necessarily mean we do not
have the permission to lock the subscription, It seems to me we only need to
disallow changing the subscription data in this case. In
DropSubscription, we take a lock on the subscription regardless of
password_required.
Best Regards,
Hou zj
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 10:15 Amit Kapila <[email protected]>
parent: Zhijie Hou (Fujitsu) <[email protected]>
1 sibling, 1 reply; 701+ messages in thread
From: Amit Kapila @ 2026-07-03 10:15 UTC (permalink / raw)
To: Zhijie Hou (Fujitsu) <[email protected]>; +Cc: Bertrand Drouvot <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Fri, Jul 3, 2026 at 1:38 PM Zhijie Hou (Fujitsu)
<[email protected]> wrote:
>
> On Friday, July 3, 2026 1:53 PM Bertrand Drouvot <[email protected]> wrote:
> >
> > > but given the patch's simplicity, I recommend backpatching.
> >
> > That's right but that would only improve error messages. That said, looking
> > closer, they are elog() ones, so "not expected" to occur so yeah backpatch
> > does make sense.
>
> +1 for backpatching, even if it's rare, the "ERROR: tuple concurrently updated"
> message seems confusing to me.
>
I also think backpatching makes sense. BTW, I have a comment:
+ heap_freetuple(tup);
+ tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
+ CStringGetDatum(stmt->subname));
heap_freetuple() could be done before acquiring the lock, is there a
reason to keep it after lock?
> >
> > That said, what about also fixing DropSubscription() like in the 0002 attached?
> > (that would also produce those elog() messages in case of concurrent DROP or
> > ALTER).
>
> For the patch, I'm not sure if we must repeat the checks twice. Could we
> simply move the original checks to after we take the lock? At least, the
> GetSubscription() call and the password check can be moved there and old codes
> can be deleted.
>
Isn't the same true for the AlterSubscription() case as well? Also, I
noticed that AlterPublication() does the same trick but it uses
PUBLICATIONOID cacheid, so shouldn't we use SUBSCRIPTIONOID cacheid
here as well? I think this is to prevent the case where the same name
pub/sub is recreated after lock.
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
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 517d46f47f9..e23b366a87d 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,17 +2567,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()))
@@ -2587,12 +2578,39 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
+
/*
* Lock the subscription so nobody else can do anything with it (including
* the replication workers).
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription
@ 2026-07-03 11:54 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 11:54 UTC (permalink / raw)
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 protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 40 ++++++++++++++++++-------
1 file changed, 29 insertions(+), 11 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 517d46f47f9..c9e7fbdb47b 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -2567,25 +2567,15 @@ 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()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_SUBSCRIPTION,
stmt->subname);
- /* DROP hook for the subscription being removed */
- InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+ ReleaseSysCache(tup);
/*
* Lock the subscription so nobody else can do anything with it (including
@@ -2593,6 +2583,34 @@ DropSubscription(DropSubscriptionStmt *stmt, bool isTopLevel)
*/
LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ /* DROP hook for the subscription being removed */
+ InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
+
+ /*
+ * Re-read the subscription tuple after acquiring the lock. A concurrent
+ * ALTER or DROP may have committed before we acquired the lock.
+ */
+ tup = SearchSysCache1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ 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
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0003-Add-invalidation-based-retry-loop-for-Alter-Drop-.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--b+bwjVhifbzgesmw
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v4-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 12:28 Bertrand Drouvot <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 12:28 UTC (permalink / raw)
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 moving the GetSubscription() call, the password_required privilege check,
and the local variable assignments to after LockSharedObject(), with a re-read of
the subscription tuple to ensure we operate on current catalog state.
Remark:
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 <[email protected]>
Reviewed-by: Dilip Kumar <[email protected]>
Reviewed-by: Hayato Kuroda (Fujitsu) <[email protected]>
Reviewed-by: Zhijie Hou <[email protected]>
Reviewed-by: Amit Kapila <[email protected]>
Discussion: https://postgr.es/m/akZUpiDa1UfmzYxL%40bdtpg
---
src/backend/commands/subscriptioncmds.c | 30 +++++++++++++++++++------
1 file changed, 23 insertions(+), 7 deletions(-)
100.0% src/backend/commands/
diff --git a/src/backend/commands/subscriptioncmds.c b/src/backend/commands/subscriptioncmds.c
index 4292e7fb8f4..517d46f47f9 100644
--- a/src/backend/commands/subscriptioncmds.c
+++ b/src/backend/commands/subscriptioncmds.c
@@ -1686,6 +1686,25 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
orig_conninfo_needed = false;
}
+ heap_freetuple(tup);
+
+ /* 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.
+ */
+ tup = SearchSysCacheCopy1(SUBSCRIPTIONOID, ObjectIdGetDatum(subid));
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("subscription \"%s\" does not exist",
+ stmt->subname)));
+
+ form = (Form_pg_subscription) GETSTRUCT(tup);
+
/*
* Skip ACL checks on the subscription's foreign server, if any. If
* changing the server (or replacing it with a raw connection), then the
@@ -1695,11 +1714,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.
@@ -1710,8 +1724,10 @@ AlterSubscription(ParseState *pstate, AlterSubscriptionStmt *stmt,
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.")));
- /* Lock the subscription so nobody else can do anything with it. */
- LockSharedObject(SubscriptionRelationId, subid, 0, AccessExclusiveLock);
+ 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));
--
2.34.1
--3eAVwo4vDNlHc8RC
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
filename="v3-0002-Re-read-subscription-state-after-lock-in-DropSubs.patch"
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-03 15:39 Bertrand Drouvot <[email protected]>
parent: Amit Kapila <[email protected]>
0 siblings, 3 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-03 15:39 UTC (permalink / raw)
To: Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Fri, Jul 03, 2026 at 03:45:34PM +0530, Amit Kapila wrote:
> On Fri, Jul 3, 2026 at 1:38 PM Zhijie Hou (Fujitsu)
> <[email protected]> wrote:
> >
> > On Friday, July 3, 2026 1:53 PM Bertrand Drouvot <[email protected]> wrote:
> > >
> > > > but given the patch's simplicity, I recommend backpatching.
> > >
> > > That's right but that would only improve error messages. That said, looking
> > > closer, they are elog() ones, so "not expected" to occur so yeah backpatch
> > > does make sense.
> >
> > +1 for backpatching, even if it's rare, the "ERROR: tuple concurrently updated"
> > message seems confusing to me.
> >
>
> I also think backpatching makes sense. BTW, I have a comment:
Thanks for looking at it!
> + heap_freetuple(tup);
> + tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
> + CStringGetDatum(stmt->subname));
>
> heap_freetuple() could be done before acquiring the lock, is there a
> reason to keep it after lock?
No particular reason, could be done before. Done in 0001 attached.
>
> > >
> > > That said, what about also fixing DropSubscription() like in the 0002 attached?
> > > (that would also produce those elog() messages in case of concurrent DROP or
> > > ALTER).
> >
> > For the patch, I'm not sure if we must repeat the checks twice. Could we
> > simply move the original checks to after we take the lock? At least, the
> > GetSubscription() call and the password check can be moved there and old codes
> > can be deleted.
> >
>
> Isn't the same true for the AlterSubscription() case as well?
I think there is no need to lock if we are later going to disallow changing the
subscription data due to the password_required/superuser check.
That said moving it as suggested by Hou-san, does simplify the code and the lock
is not held for long, so done that way in 0001.
> Also, I
> noticed that AlterPublication() does the same trick but it uses
> PUBLICATIONOID cacheid, so shouldn't we use SUBSCRIPTIONOID cacheid
> here as well? I think this is to prevent the case where the same name
> pub/sub is recreated after lock.
Oh right and I did it that way in 0001 and 0002.
But while doing this and looking closely, I'm not sure AlterPublication() does
it right. Indeed, in theory, the OID could have been re-used too (between the
time we did the name resolution and the time we lock the publication). I think
what is needed is something similar to RangeVarGetRelidExtended(), means do the
name resolution, acl check (ownership) and lock acquisition, all in unison.
That's what 0003 is trying to achieve for the subscription and 0004 for the
publication.
What do you think?
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-04 08:00 Dilip Kumar <[email protected]>
parent: Bertrand Drouvot <[email protected]>
2 siblings, 1 reply; 701+ messages in thread
From: Dilip Kumar @ 2026-07-04 08:00 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Fri, Jul 3, 2026 at 9:09 PM Bertrand Drouvot
<[email protected]> wrote:
>
> Hi,
>
> On Fri, Jul 03, 2026 at 03:45:34PM +0530, Amit Kapila wrote:
> > On Fri, Jul 3, 2026 at 1:38 PM Zhijie Hou (Fujitsu)
> > <[email protected]> wrote:
> > >
> > > On Friday, July 3, 2026 1:53 PM Bertrand Drouvot <[email protected]> wrote:
> > > >
> > > > > but given the patch's simplicity, I recommend backpatching.
> > > >
> > > > That's right but that would only improve error messages. That said, looking
> > > > closer, they are elog() ones, so "not expected" to occur so yeah backpatch
> > > > does make sense.
> > >
> > > +1 for backpatching, even if it's rare, the "ERROR: tuple concurrently updated"
> > > message seems confusing to me.
> > >
> >
> > I also think backpatching makes sense. BTW, I have a comment:
>
> Thanks for looking at it!
>
> > + heap_freetuple(tup);
> > + tup = SearchSysCacheCopy2(SUBSCRIPTIONNAME, ObjectIdGetDatum(MyDatabaseId),
> > + CStringGetDatum(stmt->subname));
> >
> > heap_freetuple() could be done before acquiring the lock, is there a
> > reason to keep it after lock?
>
> No particular reason, could be done before. Done in 0001 attached.
>
> >
> > > >
> > > > That said, what about also fixing DropSubscription() like in the 0002 attached?
> > > > (that would also produce those elog() messages in case of concurrent DROP or
> > > > ALTER).
> > >
> > > For the patch, I'm not sure if we must repeat the checks twice. Could we
> > > simply move the original checks to after we take the lock? At least, the
> > > GetSubscription() call and the password check can be moved there and old codes
> > > can be deleted.
> > >
> >
> > Isn't the same true for the AlterSubscription() case as well?
>
> I think there is no need to lock if we are later going to disallow changing the
> subscription data due to the password_required/superuser check.
>
> That said moving it as suggested by Hou-san, does simplify the code and the lock
> is not held for long, so done that way in 0001.
>
> > Also, I
> > noticed that AlterPublication() does the same trick but it uses
> > PUBLICATIONOID cacheid, so shouldn't we use SUBSCRIPTIONOID cacheid
> > here as well? I think this is to prevent the case where the same name
> > pub/sub is recreated after lock.
>
> Oh right and I did it that way in 0001 and 0002.
>
> But while doing this and looking closely, I'm not sure AlterPublication() does
> it right. Indeed, in theory, the OID could have been re-used too (between the
> time we did the name resolution and the time we lock the publication). I think
> what is needed is something similar to RangeVarGetRelidExtended(), means do the
> name resolution, acl check (ownership) and lock acquisition, all in unison.
>
> That's what 0003 is trying to achieve for the subscription and 0004 for the
> publication.
>
> What do you think?
>
0003:
It looks like the implementation of DROP SUBSCRIPTION IF EXISTS has a
concurrent drop race condition in DropSubscription(). Currently, if
stmt->missing_ok is true, the initial lookup safely handles a missing
subscription. However, once a subscription is found and the code
enters the drop loop, a second internal lookup/refetch happens. If a
concurrent transaction drops the subscription after our initial check
but before this internal refetch, the code throws an error.
Essentially, the loop completely ignores the missing_ok flag during
the refetch phase. Am I missing something?
--
Regards,
Dilip Kumar
Google
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-04 08:19 Bertrand Drouvot <[email protected]>
parent: Dilip Kumar <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-04 08:19 UTC (permalink / raw)
To: Dilip Kumar <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Sat, Jul 04, 2026 at 01:30:08PM +0530, Dilip Kumar wrote:
> On Fri, Jul 3, 2026 at 9:09 PM Bertrand Drouvot
> <[email protected]> wrote:
> >
> > But while doing this and looking closely, I'm not sure AlterPublication() does
> > it right. Indeed, in theory, the OID could have been re-used too (between the
> > time we did the name resolution and the time we lock the publication). I think
> > what is needed is something similar to RangeVarGetRelidExtended(), means do the
> > name resolution, acl check (ownership) and lock acquisition, all in unison.
> >
> > That's what 0003 is trying to achieve for the subscription and 0004 for the
> > publication.
> >
> > What do you think?
> >
> 0003:
>
> It looks like the implementation of DROP SUBSCRIPTION IF EXISTS has a
> concurrent drop race condition in DropSubscription(). Currently, if
> stmt->missing_ok is true, the initial lookup safely handles a missing
> subscription. However, once a subscription is found and the code
> enters the drop loop, a second internal lookup/refetch happens. If a
> concurrent transaction drops the subscription after our initial check
> but before this internal refetch, the code throws an error.
> Essentially, the loop completely ignores the missing_ok flag during
> the refetch phase.
Good catch, will fix, thanks!
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* RE: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 02:43 Hayato Kuroda (Fujitsu) <[email protected]>
parent: Bertrand Drouvot <[email protected]>
2 siblings, 1 reply; 701+ messages in thread
From: Hayato Kuroda (Fujitsu) @ 2026-07-06 02:43 UTC (permalink / raw)
To: 'Bertrand Drouvot' <[email protected]>; Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; [email protected] <[email protected]>
Dear Bertrand,
Thanks for updating the patch. I found one issue:
```
/* DROP hook for the subscription being removed */
InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
```
I think the reporting should be after the loop, otherwise the wrong subid can be
reported. Am I missing something?
Best regards,
Hayato Kuroda
FUJITSU LIMITED
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 04:54 Amit Kapila <[email protected]>
parent: Bertrand Drouvot <[email protected]>
2 siblings, 1 reply; 701+ messages in thread
From: Amit Kapila @ 2026-07-06 04:54 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Fri, Jul 3, 2026 at 9:09 PM Bertrand Drouvot
<[email protected]> wrote:
>
> On Fri, Jul 03, 2026 at 03:45:34PM +0530, Amit Kapila wrote:
>
> But while doing this and looking closely, I'm not sure AlterPublication() does
> it right. Indeed, in theory, the OID could have been re-used too (between the
> time we did the name resolution and the time we lock the publication). I think
> what is needed is something similar to RangeVarGetRelidExtended(), means do the
> name resolution, acl check (ownership) and lock acquisition, all in unison.
>
It seems RangeVarGetRelidExtended() also doesn't do the additional
invalidation handling if the caller already has an appropriate lock,
see comments [1]. Apart from that also, I am not sure it is a good
ideal to add this additional handling in Pub/Sub DDLs as in worst case
scenario even if the OID is re-used the user will face "tuple
concurrently updated" or similar ERRORs, it won't do anything wrong.
So for such rare cases, it doesn't seem worth adding this additional
re-checking machinery. Based on the same theory, I am thinking again
whether it is worth backpatching these patches? I mean these fall into
the category of improving user facing messages during Pub/Sub DDLs, so
isn't it okay to just push this work in HEAD?
[1]:
/*
* If no lock requested, we assume the caller knows what they're
* doing. They should have already acquired a heavyweight lock on
* this relation earlier in the processing of this same statement, so
* it wouldn't be appropriate to AcceptInvalidationMessages() here, as
* that might pull the rug out from under them.
*/
if (lockmode == NoLock)
--
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 05:01 Bertrand Drouvot <[email protected]>
parent: Hayato Kuroda (Fujitsu) <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-06 05:01 UTC (permalink / raw)
To: Hayato Kuroda (Fujitsu) <[email protected]>; +Cc: Amit Kapila <[email protected]>; Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; [email protected] <[email protected]>
Hi Kuroda-san,
On Mon, Jul 06, 2026 at 02:43:20AM +0000, Hayato Kuroda (Fujitsu) wrote:
> Dear Bertrand,
>
> Thanks for updating the patch. I found one issue:
>
> ```
> /* DROP hook for the subscription being removed */
> InvokeObjectDropHook(SubscriptionRelationId, subid, 0);
>
> ```
>
> I think the reporting should be after the loop, otherwise the wrong subid can be
> reported.
Yeah, and I think this is an existing behavior not related to the patch. Currently,
InvokeObjectDropHook() is called before we lock the subscription. I think that
makes more sense to do it after the lock is acquired, so this is now changed in
0002.
Also addressing Dilip's comment in the attached.
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 05:43 Bertrand Drouvot <[email protected]>
parent: Amit Kapila <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-06 05:43 UTC (permalink / raw)
To: Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Mon, Jul 06, 2026 at 10:24:26AM +0530, Amit Kapila wrote:
> On Fri, Jul 3, 2026 at 9:09 PM Bertrand Drouvot
> <[email protected]> wrote:
> >
> > On Fri, Jul 03, 2026 at 03:45:34PM +0530, Amit Kapila wrote:
> >
> > But while doing this and looking closely, I'm not sure AlterPublication() does
> > it right. Indeed, in theory, the OID could have been re-used too (between the
> > time we did the name resolution and the time we lock the publication). I think
> > what is needed is something similar to RangeVarGetRelidExtended(), means do the
> > name resolution, acl check (ownership) and lock acquisition, all in unison.
> >
>
> It seems RangeVarGetRelidExtended() also doesn't do the additional
> invalidation handling if the caller already has an appropriate lock,
> see comments [1].
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 09:37 Amit Kapila <[email protected]>
parent: Bertrand Drouvot <[email protected]>
0 siblings, 1 reply; 701+ messages in thread
From: Amit Kapila @ 2026-07-06 09:37 UTC (permalink / raw)
To: Bertrand Drouvot <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
On Mon, Jul 6, 2026 at 11:13 AM Bertrand Drouvot
<[email protected]> wrote:
>
> On Mon, Jul 06, 2026 at 10:24:26AM +0530, Amit Kapila wrote:
> > On Fri, Jul 3, 2026 at 9:09 PM Bertrand Drouvot
> > <[email protected]> wrote:
> > >
> > > On Fri, Jul 03, 2026 at 03:45:34PM +0530, Amit Kapila wrote:
> > >
> > > But while doing this and looking closely, I'm not sure AlterPublication() does
> > > it right. Indeed, in theory, the OID could have been re-used too (between the
> > > time we did the name resolution and the time we lock the publication). I think
> > > what is needed is something similar to RangeVarGetRelidExtended(), means do the
> > > name resolution, acl check (ownership) and lock acquisition, all in unison.
> > >
> >
> > It seems RangeVarGetRelidExtended() also doesn't do the additional
> > invalidation handling if the caller already has an appropriate lock,
> > see comments [1].
>
> From what I can see, the NoLock callers of RangeVarGetRelidExtended(), are for
> callers that don't modify objects (they are "read only" callers). The only
> exception is nextval() but there is an XXX that mentions it.
>
> Here we modify the subscription or publication, so I don't think we are in the
> NoLock spirit of RangeVarGetRelidExtended().
>
IIUC, here the risk is that during the first read and before we take
the Lock, if the same OID is reused for a different subscription then
we may end up modifying an unintended subscription. I think that is a
theoretical risk rather than a practical one. We already note similar
risk at other places, like see comments atop GetNewOidWithIndex(Since
the OID is not immediately inserted into the table, there is a race
condition here; but a problem could occur only if someone else managed
to cycle through 2^32 OIDs and generate the same OID before we finish
inserting our row. This seems unlikely to be a problem.). Similarly
comments atop GetNewRelFileNumber( As with GetNewOidWithIndex(), there
is some theoretical risk of a race) made a note of similar risk. I
feel we should note this in comments rather than trying to add
additional code to handle it.
As per my understanding the loop exists in RangeVarGetRelidExtended()
because relation lookup follows the name, and no lock can pin a
name->OID binding, so the binding can be rebound by concurrent DDL
between lookup and lock. Concretely, our lock protects relation X's
OID, but it can't stop someone renaming X away and handing X's old
name to a different relation Y. The lockable thing (the OID) and the
thing that changes (the name binding) are different objects. The loop
detects exactly this: acquiring the lock runs
AcceptInvalidationMessages(), and if any invalidations arrived while
we waited (inval_count == SharedInvalidMessageCounter), it re-resolves
the name. If the name now maps to a different OID than the one we
locked, it releases the old lock and locks the new OID. It repeats
until the name resolves to the same OID across a lock acquisition —
i.e. until the binding is stable while locked. OTOH, the subscription
path follows the locked OID instead, so it needs only a single
re-read.
With Regards,
Amit Kapila.
^ permalink raw reply [nested|flat] 701+ messages in thread
* Re: Re-read subscription state after lock in AlterSubscription
@ 2026-07-06 14:53 Bertrand Drouvot <[email protected]>
parent: Amit Kapila <[email protected]>
0 siblings, 0 replies; 701+ messages in thread
From: Bertrand Drouvot @ 2026-07-06 14:53 UTC (permalink / raw)
To: Amit Kapila <[email protected]>; +Cc: Zhijie Hou (Fujitsu) <[email protected]>; Dilip Kumar <[email protected]>; Hayato Kuroda (Fujitsu) <[email protected]>; [email protected] <[email protected]>
Hi,
On Mon, Jul 06, 2026 at 03:07:24PM +0530, Amit Kapila wrote:
> On Mon, Jul 6, 2026 at 11:13 AM Bertrand Drouvot
> <[email protected]> wrote:
> >
> > > It seems RangeVarGetRelidExtended() also doesn't do the additional
> > > invalidation handling if the caller already has an appropriate lock,
> > > see comments [1].
> >
> > From what I can see, the NoLock callers of RangeVarGetRelidExtended(), are for
> > callers that don't modify objects (they are "read only" callers). The only
> > exception is nextval() but there is an XXX that mentions it.
> >
> > Here we modify the subscription or publication, so I don't think we are in the
> > NoLock spirit of RangeVarGetRelidExtended().
> >
>
> IIUC, here the risk is that during the first read and before we take
> the Lock, if the same OID is reused for a different subscription then
> we may end up modifying an unintended subscription. I think that is a
> theoretical risk rather than a practical one.
I agree OID reuse itself is theoretical.
> As per my understanding the loop exists in RangeVarGetRelidExtended()
> because relation lookup follows the name, and no lock can pin a
> name->OID binding, so the binding can be rebound by concurrent DDL
> between lookup and lock. Concretely, our lock protects relation X's
> OID, but it can't stop someone renaming X away and handing X's old
> name to a different relation Y.
Not sure it's only about renaming. The commit message of 4240e429d0c mentions
"This was particularly problematic in the case where a table had been dropped
and recreated". b3ad5d02c9c also used the same logic and reasoning "avoids
needlessly failing when the object of interest is concurrently dropped and
recreated".
Also in 4240e429d0c: "there's nothing at all here to guard against similar race
conditions for non-relations": I think that subscriptions and publications are
among those non-relations cases.
> The lockable thing (the OID) and the
> thing that changes (the name binding) are different objects. The loop
> detects exactly this: acquiring the lock runs
> AcceptInvalidationMessages(), and if any invalidations arrived while
> we waited (inval_count == SharedInvalidMessageCounter), it re-resolves
> the name. If the name now maps to a different OID than the one we
> locked, it releases the old lock and locks the new OID. It repeats
> until the name resolves to the same OID across a lock acquisition —
> i.e. until the binding is stable while locked. OTOH, the subscription
> path follows the locked OID instead, so it needs only a single
> re-read.
I think that's for example what RemoveRelations() was doing before 4240e429d0c
and what get_object_address() was doing before b3ad5d02c9c:
1/ Resolve name to OID
2/ Lock by OID
3/ Check if it still exists
4/ If gone then elog(ERROR..
but has been changed in b3ad5d02c9c with a retry loop.
Also looking at get_object_address(), I can see that it handles publications and
subscriptions:
case OBJECT_PUBLICATION:
case OBJECT_SUBSCRIPTION:
address = get_object_address_unqualified(objtype,
castNode(String, object), missing_ok);
and that DROP PUBLICATION goes through it, so that it already benefits from the
retry loop in get_object_address().
DROP SUBSCRIPTION however has its own dedicated code path and does not go through
get_object_address(): 0003 adds the retry loop for it. And if DROP already uses
the retry loop then ALTER should probably use it too (also done in 0003 and 0004).
Regards,
--
Bertrand Drouvot
PostgreSQL Contributors Team
RDS Open Source Databases
Amazon Web Services: https://aws.amazon.com
^ permalink raw reply [nested|flat] 701+ messages in thread
end of thread, other threads:[~2026-07-06 14:53 UTC | newest]
Thread overview: 701+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-12-20 01:25 [PATCH v34 11/11] Add documentations about Incremental View Maintenance Yugo Nagata <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 11:07 [PATCH v1] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 12:08 Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-02 12:27 ` Re: Re-read subscription state after lock in AlterSubscription Dilip Kumar <[email protected]>
2026-07-02 12:48 ` RE: Re-read subscription state after lock in AlterSubscription Hayato Kuroda (Fujitsu) <[email protected]>
2026-07-02 13:20 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 03:13 ` RE: Re-read subscription state after lock in AlterSubscription Hayato Kuroda (Fujitsu) <[email protected]>
2026-07-03 04:19 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 04:50 ` Re: Re-read subscription state after lock in AlterSubscription Dilip Kumar <[email protected]>
2026-07-03 05:52 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 08:08 ` RE: Re-read subscription state after lock in AlterSubscription Zhijie Hou (Fujitsu) <[email protected]>
2026-07-03 09:03 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 09:56 ` RE: Re-read subscription state after lock in AlterSubscription Zhijie Hou (Fujitsu) <[email protected]>
2026-07-03 10:15 ` Re: Re-read subscription state after lock in AlterSubscription Amit Kapila <[email protected]>
2026-07-03 15:39 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-04 08:00 ` Re: Re-read subscription state after lock in AlterSubscription Dilip Kumar <[email protected]>
2026-07-04 08:19 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-06 02:43 ` RE: Re-read subscription state after lock in AlterSubscription Hayato Kuroda (Fujitsu) <[email protected]>
2026-07-06 05:01 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-06 04:54 ` Re: Re-read subscription state after lock in AlterSubscription Amit Kapila <[email protected]>
2026-07-06 05:43 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-06 09:37 ` Re: Re-read subscription state after lock in AlterSubscription Amit Kapila <[email protected]>
2026-07-06 14:53 ` Re: Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:17 [PATCH v2 1/2] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 05:18 [PATCH v2 2/2] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v3 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 11:54 [PATCH v4 2/4] Re-read subscription state after lock in DropSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v3 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
2026-07-03 12:28 [PATCH v4 1/4] Re-read subscription state after lock in AlterSubscription Bertrand Drouvot <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox