From 67e407e18c64dff51904232ef98f3f992dd82d03 Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Tue, 9 Jun 2026 12:48:19 +0900
Subject: [PATCH 1/2] Reproducer for the streaming replication

---
 src/test/recovery/meson.build    |   1 +
 src/test/recovery/t/099_repro.pl | 123 +++++++++++++++++++++++++++++++
 2 files changed, 124 insertions(+)
 create mode 100644 src/test/recovery/t/099_repro.pl

diff --git a/src/test/recovery/meson.build b/src/test/recovery/meson.build
index 9eb8ed11425..bfd06a06124 100644
--- a/src/test/recovery/meson.build
+++ b/src/test/recovery/meson.build
@@ -62,6 +62,7 @@ tests += {
       't/051_effective_wal_level.pl',
       't/052_checkpoint_segment_missing.pl',
       't/053_standby_login_event_trigger.pl',
+      't/099_repro.pl',
     ],
   },
 }
diff --git a/src/test/recovery/t/099_repro.pl b/src/test/recovery/t/099_repro.pl
new file mode 100644
index 00000000000..34a97bb19f3
--- /dev/null
+++ b/src/test/recovery/t/099_repro.pl
@@ -0,0 +1,123 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+# Reproducer wrong timeline can be chosen during the promotion for the
+# streaming replication.
+
+use strict;
+use warnings FATAL => 'all';
+
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+if ($ENV{enable_injection_points} ne 'yes')
+{
+	plan skip_all => 'Injection points not supported by this build';
+}
+
+my ($stdout, $stderr, $handle);
+
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+my $default_timeout = $PostgreSQL::Test::Utils::timeout_default;
+
+# Name for the physical slot on primary
+my $primary_slotname = 'primary_physical';
+my $standby_physical_slotname = 'standby_physical';
+
+########################
+# Initialize primary node
+########################
+
+$node_primary->init(allows_streaming => 1, has_archiving => 1);
+$node_primary->append_conf(
+	'postgresql.conf', q{
+wal_level = 'logical'
+max_replication_slots = 4
+max_wal_senders = 4
+autovacuum = off
+});
+$node_primary->dump_info;
+$node_primary->start;
+
+# Check if the extension injection_points is available, as it may be
+# possible that this script is run with installcheck, where the module
+# would not be installed by default.
+if (!$node_primary->check_extension('injection_points'))
+{
+	plan skip_all => 'Extension injection_points not installed';
+}
+
+$node_primary->safe_psql('postgres', 'CREATE EXTENSION injection_points;');
+
+$node_primary->safe_psql('postgres',
+	qq[SELECT * FROM pg_create_physical_replication_slot('$primary_slotname');]
+);
+
+my $backup_name = 'b1';
+$node_primary->backup($backup_name);
+
+# Some tests need to wait for VACUUM to be replayed. But vacuum does not flush
+# WAL. An insert into flush_wal outside transaction does guarantee a flush.
+$node_primary->psql('postgres', q[CREATE TABLE flush_wal();]);
+
+#######################
+# Initialize standby node
+#######################
+
+$node_standby->init_from_backup(
+	$node_primary, $backup_name,
+	has_streaming => 1,
+	has_restoring => 1);
+$node_standby->append_conf(
+	'postgresql.conf',
+	qq[primary_slot_name = '$primary_slotname'
+       max_replication_slots = 5]);
+$node_standby->start;
+$node_primary->wait_for_replay_catchup($node_standby);
+
+$node_standby->safe_psql('postgres',
+    "SELECT pg_create_physical_replication_slot('physical_standby');"
+);
+
+# Attach injection point to pause startup after WAL segment cleanup
+# but before RecoveryInProgress() flips to false.
+$node_standby->safe_psql('postgres',
+	"SELECT injection_points_attach('promotion-after-wal-segment-cleanup', 'wait');"
+);
+
+# Promote with no-wait so we can synchronize with the injection point.
+$node_standby->safe_psql('postgres', "SELECT pg_promote(false)");
+
+# Wait for startup to pause after removing old timeline WAL segments.
+$node_standby->wait_for_event('startup',
+	'promotion-after-wal-segment-cleanup');
+
+my $stream_dir = $node_primary->basedir . '/archive_wal';
+mkdir($stream_dir);
+
+my $log_offset = -s $node_standby->logfile;
+
+# Start pg_receivewal
+$handle = IPC::Run::start(
+	[
+        'pg_receivewal',
+        '--directory' => $stream_dir,
+        '--dbname' => $node_standby->connstr('postgres'),
+        '--slot' => 'physical_standby',
+        '--no-loop',
+	],
+	'>' => \$stdout,
+	'2>' => \$stderr,
+	IPC::Run::timeout($default_timeout));
+
+# XXX wait a bit to make sure pg_receivewal has started
+sleep(1);
+
+# Check the log content
+ok( !$node_standby->log_contains(
+		"requested WAL segment [0-9A-F]+ has already been removed",
+		$log_offset),
+	'check that required WAL segments are still available');
+
+done_testing();
-- 
2.52.0

