From ad7371272d9e12f5bfeb6c89474916772480f04e Mon Sep 17 00:00:00 2001
From: Vignesh C <vignesh21@gmail.com>
Date: Sat, 18 Jul 2026 18:28:02 +0530
Subject: [PATCH] Test: add test for REFRESH SEQUENCES stopping a running
 worker

ALTER SUBSCRIPTION ... REFRESH SEQUENCES stops any running sequence
synchronization worker before resetting sequences to INIT, rather
than failing or letting the worker apply a stale value it had
already fetched. Add a test to 036_sequences.pl that uses the
injection point at sequencesync-before-copy-sequence to block a
worker after it has fetched a sequence's publisher value but before
applying it, issue a second REFRESH SEQUENCES while the worker is
blocked, and confirm it succeeds, resets the sequence to INIT, and
that a freshly launched worker converges on the publisher's latest
value rather than the stale one.
---
 .../replication/logical/sequencesync.c        |  4 +
 src/test/subscription/t/036_sequences.pl      | 84 +++++++++++++++++++
 2 files changed, 88 insertions(+)

diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 63ad46d7fd7..5d7d8505d9d 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -65,6 +65,7 @@
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/guc.h"
+#include "utils/injection_point.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -561,8 +562,11 @@ copy_sequences(WalReceiverConn *conn)
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
 			if (sync_status == COPYSEQ_SUCCESS)
+			{
+				INJECTION_POINT("sequencesync-before-copy-sequence", NULL);
 				sync_status = copy_sequence(seqinfo,
 											sequence_rel->rd_rel->relowner);
+			}
 
 			switch (sync_status)
 			{
diff --git a/src/test/subscription/t/036_sequences.pl b/src/test/subscription/t/036_sequences.pl
index 77ac9386cd8..1328cf47805 100644
--- a/src/test/subscription/t/036_sequences.pl
+++ b/src/test/subscription/t/036_sequences.pl
@@ -242,6 +242,90 @@ $node_publisher->safe_psql(
 $node_subscriber->poll_query_until('postgres', $synced_query)
   or die "Timed out while waiting for subscriber to synchronize data";
 
+##########
+# ALTER SUBSCRIPTION ... REFRESH SEQUENCES must not fail merely because a
+# sequencesync worker is already running for this subscription. Instead, it
+# must stop the running worker and reset every sequence to INIT again, so a
+# freshly launched worker discards any stale value the stopped worker had
+# already fetched but not yet applied, and resynchronizes with the
+# publisher's latest values.
+##########
+
+my $injection_points_supported =
+  $node_subscriber->check_extension('injection_points');
+
+if ($injection_points_supported != 0)
+{
+	$node_subscriber->append_conf('postgresql.conf',
+		"shared_preload_libraries = 'injection_points'");
+	$node_subscriber->restart;
+
+	$node_subscriber->safe_psql('postgres',
+		"CREATE EXTENSION injection_points;");
+
+	# Block the sequencesync worker just before it applies a sequence's
+	# fetched values.
+	$node_subscriber->safe_psql('postgres',
+		"SELECT injection_points_attach('sequencesync-before-copy-sequence', 'wait');"
+	);
+
+	$node_subscriber->safe_psql('postgres',
+		"ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+	$node_subscriber->wait_for_event('logical replication sequencesync worker',
+		'sequencesync-before-copy-sequence');
+
+	$result = $node_subscriber->safe_psql('postgres',
+		"SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass"
+	);
+	is($result, 'i', 'sequence is still in INIT state while the worker is blocked');
+
+	# Advance the sequence on the publisher, so the value already fetched by
+	# the blocked worker becomes stale.
+	$node_publisher->safe_psql(
+		'postgres', qq(
+		INSERT INTO regress_seq_test SELECT nextval('regress_s3') FROM generate_series(1,200);
+	));
+
+	# A second REFRESH SEQUENCES while the worker is still blocked must
+	# succeed: it stops the blocked worker (discarding its stale fetched
+	# value) and resets the sequence to INIT again, rather than failing
+	# because a sequence synchronization worker is running.
+	$node_subscriber->safe_psql('postgres',
+		"ALTER SUBSCRIPTION regress_seq_sub REFRESH SEQUENCES");
+
+	$result = $node_subscriber->safe_psql('postgres',
+		"SELECT srsubstate FROM pg_subscription_rel WHERE srrelid = 'regress_s3'::regclass"
+	);
+	is($result, 'i',
+		'sequence remains in INIT state after the second REFRESH SEQUENCES stops the worker'
+	);
+
+	# Detach the injection point so the freshly launched worker is not
+	# blocked by it too.
+	$node_subscriber->safe_psql('postgres',
+		"SELECT injection_points_wakeup('sequencesync-before-copy-sequence');
+		 SELECT injection_points_detach('sequencesync-before-copy-sequence');"
+	);
+
+	$node_subscriber->poll_query_until('postgres', $synced_query)
+	  or die "Timed out while waiting for subscriber to synchronize data";
+
+	# The freshly launched worker must reflect the publisher's latest value
+	# (200), not the stale value (1) already fetched by the stopped worker.
+	$result = $node_subscriber->safe_psql(
+		'postgres', qq(
+		SELECT last_value, is_called FROM regress_s3;
+	));
+	is($result, '200|t',
+		'REFRESH SEQUENCES resynchronizes with the latest publisher value after stopping a running worker'
+	);
+}
+else
+{
+	note 'Injection points not supported by this build';
+}
+
 ##########
 # ALTER SUBSCRIPTION ... REFRESH PUBLICATION should report an error when:
 # a) sequence definitions differ between the publisher and subscriber, or
-- 
2.50.1 (Apple Git-155)

