From 9c91b2354657fb266ae810078ad37705cc9738d0 Mon Sep 17 00:00:00 2001
From: shihao zhong <zhong950419@gmail.com>
Date: Sun, 25 Oct 2026 00:10:07 -0400
Subject: [PATCH 1/2] Test WAL buffer reads by a new walsender

---
 src/test/recovery/meson.build                 |   1 +
 .../recovery/t/062_wal_buffers_reconnect.pl   | 103 ++++++++++++++++++
 2 files changed, 104 insertions(+)
 create mode 100644 src/test/recovery/t/062_wal_buffers_reconnect.pl

diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index d68cfb149f0..5087126421d 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -70,6 +70,7 @@ tests += {
       't/059_remote_apply_status_interval.pl',
       't/060_multixact_crashed_updater.pl',
       't/061_wal_buffers_recycling.pl',
+      't/062_wal_buffers_reconnect.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/062_wal_buffers_reconnect.pl b/src/test/recovery/t/062_wal_buffers_reconnect.pl
new file mode 100644
index 00000000000..50d38a749e8
--- /dev/null
+++ b/src/test/recovery/t/062_wal_buffers_reconnect.pl
@@ -0,0 +1,103 @@
+# Variant of Kirill's 061 test: after the checkpoint removes the segment,
+# terminate the paused sender instead of waking it.  The receiver reconnects
+# and a new sender starts at the same position.  Its reader has
+# seg.ws_tli == 0 until the first disk read, so WALReadFromBuffers() returns 0.
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Time::HiRes qw(usleep);
+
+if (!defined($ENV{enable_injection_points})
+	|| $ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my $primary = PostgreSQL::Test::Cluster->new('primary');
+$primary->init(allows_streaming => 1, extra => ['--wal-segsize=1']);
+$primary->append_conf('postgresql.conf', qq(
+shared_preload_libraries = 'injection_points'
+archive_mode = off
+wal_buffers = '32MB'
+min_wal_size = '2MB'
+max_wal_size = '4MB'
+wal_keep_size = 0
+checkpoint_timeout = '1h'
+wal_sender_timeout = 0
+autovacuum = off
+));
+$primary->start;
+$primary->safe_psql('postgres',
+	'CREATE EXTENSION injection_points; CREATE TABLE buffers_test (i int)');
+$primary->backup('base');
+
+my $receiver = PostgreSQL::Test::Cluster->new('receiver');
+$receiver->init_from_backup($primary, 'base', has_streaming => 1);
+$receiver->append_conf('postgresql.conf', qq(
+restore_command = ''
+wal_receiver_timeout = 0
+wal_retrieve_retry_interval = '100ms'
+));
+$receiver->start;
+$primary->safe_psql('postgres', 'INSERT INTO buffers_test VALUES (1)');
+my $initial_lsn = $primary->lsn('insert');
+$primary->wait_for_catchup($receiver, 'replay', $initial_lsn);
+my $old_segment = $primary->safe_psql('postgres',
+	"SELECT pg_walfile_name('$initial_lsn')");
+my $sender_pid = $primary->safe_psql('postgres',
+	'SELECT pid FROM pg_stat_replication');
+
+$primary->safe_psql(
+	'postgres',
+	q{SELECT injection_points_attach('walwriter-before-background-flush', 'wait')});
+$primary->wait_for_event('walwriter', 'walwriter-before-background-flush');
+
+$primary->safe_psql('postgres',
+	q{SELECT injection_points_attach('walsender-before-wal-read', 'wait')});
+$primary->safe_psql('postgres', 'INSERT INTO buffers_test VALUES (2)');
+$primary->wait_for_event('walsender', 'walsender-before-wal-read');
+
+$primary->safe_psql('postgres', q{
+SELECT pg_logical_emit_message(false, 'fill', repeat('x', 1024 * 1024))
+FROM generate_series(1, 12);
+CHECKPOINT;
+});
+my $target_lsn = $primary->lsn('insert');
+ok(!-e $primary->data_dir . "/pg_wal/$old_segment",
+	'checkpoint removed the segment needed by the paused sender')
+  or BAIL_OUT('checkpoint did not remove the required local segment');
+
+# Kill the paused sender, so that the receiver reconnects.
+my $log_offset = -s $primary->logfile;
+$primary->safe_psql('postgres', qq{
+SELECT injection_points_detach('walsender-before-wal-read');
+SELECT pg_terminate_backend($sender_pid);
+});
+
+# Poll for up to 30 seconds, without the default 180 second timeout.
+my $caught_up = 0;
+for (1 .. 300)
+{
+	if ($receiver->safe_psql('postgres',
+			"SELECT pg_last_wal_replay_lsn() >= '$target_lsn'::pg_lsn") eq 't')
+	{
+		$caught_up = 1;
+		last;
+	}
+	usleep(100_000);
+}
+my $removed = $primary->log_contains('has already been removed', $log_offset);
+note "caught_up=$caught_up removed_error_in_log=" . ($removed ? 1 : 0);
+ok($caught_up, 'receiver catches up after reconnecting');
+ok(!$removed, 'no "already been removed" error after reconnect');
+
+$primary->safe_psql('postgres', q{
+SELECT injection_points_detach('walwriter-before-background-flush');
+SELECT injection_points_wakeup('walwriter-before-background-flush');
+});
+$receiver->stop;
+$primary->stop;
+done_testing();
-- 
2.37.1 (Apple Git-137.1)

