v6-0001-PG16-Fix-pg_get_publication_tables-race-with-conc.patch
application/x-patch
Filename: v6-0001-PG16-Fix-pg_get_publication_tables-race-with-conc.patch
Type: application/x-patch
Part: 3
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v6-0001
Subject: PG16 - Fix pg_get_publication_tables race with concurrent DROP TABLE
| File | + | − |
|---|---|---|
| src/backend/catalog/pg_publication.c | 32 | 6 |
| src/tools/pgindent/typedefs.list | 1 | 0 |
From 670200a535f50836ca860868036902470d5408f2 Mon Sep 17 00:00:00 2001
From: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Date: Sun, 3 May 2026 20:24:10 +0000
Subject: [PATCH v6] PG16 - Fix pg_get_publication_tables race with concurrent
DROP TABLE
pg_get_publication_tables() collects table OIDs without locks on
the first call, then opens each table on subsequent calls. If a
table is dropped in between, the function errors with
"could not open relation with OID". This is common in environments
where many tables are being created and dropped while
pg_publication_tables view is queried, such as with
FOR ALL TABLES publications.
The bug was introduced by b7ae03953690 in PG16.
Fix by skipping concurrently dropped tables instead of erroring
out. Tables created after the list is built are simply not present
in the result set, which is expected point-in-time behavior.
Author: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: shveta malik <shveta.malik@gmail.com>
Reviewed-by: Ajin Cherian <itsajin@gmail.com>
Discussion: https://www.postgresql.org/message-id/CALj2ACVYYooWH-5tJ6cPKkU%2BmutVxwb_z4S%2BqAi-zdrFqxXE2Q%40mail.gmail.com
Backpatch-through: 16
---
src/backend/catalog/pg_publication.c | 38 +++++++++++++++++++++++-----
src/tools/pgindent/typedefs.list | 1 +
2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index c488b6370b6..e773308539b 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -56,6 +56,13 @@ typedef struct
static void publication_translate_columns(Relation targetrel, List *columns,
int *natts, AttrNumber **attrs);
+/* State for pg_get_publication_tables SRF */
+typedef struct
+{
+ List *table_infos; /* list of published_rel */
+ int curr_idx; /* current index into table_infos */
+} publication_tables_state;
+
/*
* Check if relation can be in given publication and throws appropriate
* error if not.
@@ -1058,7 +1065,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
{
#define NUM_PUBLICATION_TABLES_ELEM 4
FuncCallContext *funcctx;
- List *table_infos = NIL;
+ publication_tables_state *ptstate;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
@@ -1066,6 +1073,7 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
TupleDesc tupdesc;
MemoryContext oldcontext;
ArrayType *arr;
+ List *table_infos = NIL;
Datum *elems;
int nelems,
i;
@@ -1165,24 +1173,36 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
funcctx->user_fctx = (void *) table_infos;
+ /* Store the state to be used across SRF calls. */
+ ptstate = palloc_object(publication_tables_state);
+ ptstate->table_infos = table_infos;
+ ptstate->curr_idx = 0;
+ funcctx->user_fctx = ptstate;
+
MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
- table_infos = (List *) funcctx->user_fctx;
+ ptstate = (publication_tables_state *) funcctx->user_fctx;
- if (funcctx->call_cntr < list_length(table_infos))
+ while (ptstate->curr_idx < list_length(ptstate->table_infos))
{
HeapTuple pubtuple = NULL;
HeapTuple rettuple;
Publication *pub;
- published_rel *table_info = (published_rel *) list_nth(table_infos, funcctx->call_cntr);
+ published_rel *table_info = (published_rel *) list_nth(ptstate->table_infos, ptstate->curr_idx);
Oid relid = table_info->relid;
Oid schemaid = get_rel_namespace(relid);
Datum values[NUM_PUBLICATION_TABLES_ELEM] = {0};
bool nulls[NUM_PUBLICATION_TABLES_ELEM] = {0};
+ ptstate->curr_idx++;
+
+ /* Skip if the relation has been concurrently dropped. */
+ if (!OidIsValid(schemaid))
+ continue;
+
/*
* Form tuple with appropriate data.
*/
@@ -1225,12 +1245,18 @@ pg_get_publication_tables(PG_FUNCTION_ARGS)
/* Show all columns when the column list is not specified. */
if (nulls[2])
{
- Relation rel = table_open(relid, AccessShareLock);
+ Relation rel = try_table_open(relid, AccessShareLock);
int nattnums = 0;
int16 *attnums;
- TupleDesc desc = RelationGetDescr(rel);
+ TupleDesc desc;
int i;
+ /* Skip if the relation has been concurrently dropped. */
+ if (rel == NULL)
+ continue;
+
+ desc = RelationGetDescr(rel);
+
attnums = (int16 *) palloc(desc->natts * sizeof(int16));
for (i = 0; i < desc->natts; i++)
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index db43f32bcdd..8a93cc8dfc4 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -3652,6 +3652,7 @@ pthread_mutex_t
pthread_once_t
pthread_t
ptrdiff_t
+publication_tables_state
published_rel
pull_var_clause_context
pull_varattnos_context
--
2.47.3