v2-0001-Fix-stale-relation-close-in-sequence-synchronization.patch
application/octet-stream
Filename: v2-0001-Fix-stale-relation-close-in-sequence-synchronization.patch
Type: application/octet-stream
Part: 0
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 v2-0001
Subject: Fix stale relation close in sequence synchronization
| File | + | − |
|---|---|---|
| src/backend/replication/logical/sequencesync.c | 5 | 3 |
| src/test/subscription/t/036_sequences.pl | 36 | 0 |
From 75672fedf247f045ee772cf365ff62e0c0bcacdb Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Tue, 28 Apr 2026 16:37:15 +0530
Subject: [PATCH v2] Fix stale relation close in sequence synchronization
copy_sequences() processes publisher sequence data one result row at a time,
but kept the Relation pointer used for the local sequence at batch scope. If
pg_get_sequence_data() returned NULL sequence data for a later row,
get_and_validate_seq_info() could return COPYSEQ_SKIPPED before assigning a
new relation to that output argument. The caller could then close the stale
Relation pointer left from the previous row, tripping the relcache refcount
assertion in assert-enabled builds.
This is not limited to concurrent drops. pg_get_sequence_data() also returns
NULL data when the publisher connection user cannot read the sequence.
Keep the caller-side Relation pointer local to each publisher result row and
initialize the output Relation in get_and_validate_seq_info(), so early return
paths cannot reuse a relation from a previous row. Add a subscription test
that syncs readable sequences followed by a published sequence for which the
replication user lacks SELECT.
---
.../replication/logical/sequencesync.c | 8 +++--
src/test/subscription/t/036_sequences.pl | 36 +++++++++++++++++++
2 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index ec7e76abf93..adc0ebadac3 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -246,6 +246,8 @@ get_and_validate_seq_info(TupleTableSlot *slot, Relation *sequence_rel,
Form_pg_sequence local_seq;
LogicalRepSequenceInfo *seqinfo_local;
+ *sequence_rel = NULL;
+
*seqidx = DatumGetInt32(slot_getattr(slot, ++col, &isnull));
Assert(!isnull);
@@ -254,8 +256,8 @@ get_and_validate_seq_info(TupleTableSlot *slot, Relation *sequence_rel,
(LogicalRepSequenceInfo *) list_nth(seqinfos, *seqidx);
/*
- * last_value can be NULL if the sequence was dropped concurrently (see
- * pg_get_sequence_data()).
+ * The sequence data can be NULL if it is not accessible on the publisher
+ * (see pg_get_sequence_data()).
*/
datum = slot_getattr(slot, ++col, &isnull);
if (isnull)
@@ -411,7 +413,6 @@ copy_sequences(WalReceiverConn *conn)
int batch_skipped_count = 0;
int batch_insuffperm_count = 0;
int batch_missing_count;
- Relation sequence_rel = NULL;
WalRcvExecResult *res;
TupleTableSlot *slot;
@@ -494,6 +495,7 @@ copy_sequences(WalReceiverConn *conn)
{
CopySeqResult sync_status;
LogicalRepSequenceInfo *seqinfo;
+ Relation sequence_rel = NULL;
int seqidx;
CHECK_FOR_INTERRUPTS();
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 471780a3585..53233920edf 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -221,4 +221,40 @@ $node_subscriber->wait_for_log(
qr/WARNING: ( [A-Z0-9]+:)? missing sequence on publisher \("public.regress_s4"\)/,
$log_offset);
+##########
+# A NULL sequence data row from the publisher must not make the subscriber
+# close the previously synchronized sequence relation again.
+##########
+
+$node_publisher->safe_psql(
+ 'postgres', qq(
+ CREATE ROLE regress_seq_repl LOGIN REPLICATION;
+ CREATE SEQUENCE regress_no_select;
+ SELECT nextval('regress_no_select');
+ GRANT USAGE ON SCHEMA public TO regress_seq_repl;
+ GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO regress_seq_repl;
+ REVOKE ALL ON SEQUENCE regress_no_select FROM PUBLIC;
+ REVOKE ALL ON SEQUENCE regress_no_select FROM regress_seq_repl;
+));
+
+$node_subscriber->safe_psql('postgres',
+ qq(CREATE SEQUENCE regress_no_select;));
+
+my $publisher_limited_connstr =
+ $node_publisher->connstr . ' dbname=postgres user=regress_seq_repl';
+$log_offset = -s $node_subscriber->logfile;
+
+$node_subscriber->safe_psql(
+ 'postgres',
+ "CREATE SUBSCRIPTION regress_seq_sub_no_select CONNECTION '$publisher_limited_connstr' PUBLICATION regress_seq_pub WITH (disable_on_error = true)"
+);
+
+$node_subscriber->wait_for_log(
+ qr/WARNING: ( [A-Z0-9]+:)? missing sequence on publisher \("public.regress_no_select"\)/,
+ $log_offset);
+
+$result = $node_subscriber->safe_psql('postgres', 'SELECT 1');
+is($result, '1',
+ 'subscriber remains running after publisher returns NULL sequence data');
+
done_testing();
--
2.34.1