v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch
application/octet-stream
Filename: v5-0005-Wake-standby_write-standby_flush-waiters-from-the.patch
Type: application/octet-stream
Part: 2
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 v5-0005
Subject: Wake standby_write/standby_flush waiters from the WAL replay loop
| File | + | − |
|---|---|---|
| src/backend/access/transam/xlogrecovery.c | 8 | 2 |
| src/test/recovery/t/049_wait_for_lsn.pl | 77 | 0 |
From 9cd16c4b02c0f6f91c5ab9d1e76ad1b0f288d725 Mon Sep 17 00:00:00 2001
From: alterego655 <824662526@qq.com>
Date: Fri, 10 Apr 2026 11:09:29 +0800
Subject: [PATCH v5 5/7] Wake standby_write/standby_flush waiters from the WAL
replay loop
The startup process only woke STANDBY_REPLAY waiters after replaying
each WAL record. STANDBY_WRITE and STANDBY_FLUSH waiters depended only
on walreceiver write/flush callbacks. As a result, replay progress alone
did not wake those waiters, and in pure archive recovery (where no
walreceiver exists) they could sleep until timeout.
Fix by also calling WaitLSNWakeup() for STANDBY_WRITE and
STANDBY_FLUSH after each replay. For the replay-floor semantics used by
GetCurrentLSNForWaitType(), replay progress is a valid lower bound for
both modes: WAL cannot be replayed unless it has already been written
and flushed locally.
This works together with the replay-position floor in
GetCurrentLSNForWaitType(). The getter ensures that a waiter woken by
replay can recheck successfully; the replay-side wakeups ensure that a
waiter already asleep is notified when replay reaches its target.
Reported-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/1957514.1775526774%40sss.pgh.pa.us
Author: Xuneng Zhou <xunengzhou@gmail.com>
---
src/backend/access/transam/xlogrecovery.c | 10 ++-
src/test/recovery/t/049_wait_for_lsn.pl | 77 +++++++++++++++++++++++
2 files changed, 85 insertions(+), 2 deletions(-)
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4f2eaa36990..73b78a83fa7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -1782,11 +1782,17 @@ PerformWalRecovery(void)
ApplyWalRecord(xlogreader, record, &replayTLI);
/*
- * Wake up processes waiting for standby replay LSN to reach
- * current replay position.
+ * Wake up processes waiting for standby replay, write, or flush
+ * LSN to reach current replay position. Replay implies that the
+ * WAL was already written and flushed to disk, so write and flush
+ * waiters can be woken at the replay position too.
*/
WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_REPLAY,
XLogRecoveryCtl->lastReplayedEndRecPtr);
+ WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE,
+ XLogRecoveryCtl->lastReplayedEndRecPtr);
+ WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_FLUSH,
+ XLogRecoveryCtl->lastReplayedEndRecPtr);
/* Exit loop if we reached inclusive recovery target */
if (recoveryStopsAfter(xlogreader))
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index 26790fda5be..d2610cf0856 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -690,6 +690,17 @@ $arc_primary->start;
$arc_primary->safe_psql('postgres',
"CREATE TABLE arc_test AS SELECT generate_series(1,10) AS a");
+# Create a helper function for server-side logging (needed by 9b).
+$arc_primary->safe_psql(
+ 'postgres', qq[
+CREATE FUNCTION arc_log_done(msg text) RETURNS void AS \$\$
+ BEGIN
+ RAISE LOG '%', msg;
+ END
+\$\$
+LANGUAGE plpgsql;
+]);
+
my $arc_backup_name = 'arc_backup';
$arc_primary->backup($arc_backup_name);
@@ -744,6 +755,72 @@ $output = $arc_standby->safe_psql(
ok($output eq "success",
"standby_flush succeeds on archive-only standby (getter fallback)");
+# 9b. Replay waker: standby_write/standby_flush waiters that go to sleep
+# (target > replay at entry) are woken when replay catches up. This tests
+# that PerformWalRecovery() calls WaitLSNWakeup for STANDBY_WRITE and
+# STANDBY_FLUSH, not just STANDBY_REPLAY.
+#
+# Pause replay, archive more WAL, start background waiters, then resume
+# replay and verify the waiters complete.
+
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_pause()");
+
+# Generate more WAL and archive it.
+$arc_primary->safe_psql('postgres',
+ "INSERT INTO arc_test VALUES (generate_series(21, 30))");
+my $arc_target_lsn2 =
+ $arc_primary->safe_psql('postgres', "SELECT pg_current_wal_insert_lsn()");
+
+my $arc_segment2 = $arc_primary->safe_psql('postgres',
+ "SELECT pg_walfile_name(pg_current_wal_lsn())");
+$arc_primary->safe_psql('postgres', "SELECT pg_switch_wal()");
+$arc_primary->poll_query_until('postgres',
+ qq{SELECT last_archived_wal >= '$arc_segment2' FROM pg_stat_archiver},
+ 't')
+ or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
+
+# Start background waiters. With replay paused, target > replay, so they
+# will sleep on WaitLatch. They can only be woken by the replay-loop
+# WaitLSNWakeup calls.
+my $arc_write_session = $arc_standby->background_psql('postgres');
+$arc_write_session->query_until(
+ qr/start/, qq[
+ \\echo start
+ WAIT FOR LSN '${arc_target_lsn2}'
+ WITH (MODE 'standby_write', timeout '30s', no_throw);
+ SELECT arc_log_done('write_arc_done');
+]);
+
+my $arc_flush_session = $arc_standby->background_psql('postgres');
+$arc_flush_session->query_until(
+ qr/start/, qq[
+ \\echo start
+ WAIT FOR LSN '${arc_target_lsn2}'
+ WITH (MODE 'standby_flush', timeout '30s', no_throw);
+ SELECT arc_log_done('flush_arc_done');
+]);
+
+# Verify both waiters are blocked.
+$arc_standby->poll_query_until('postgres',
+ "SELECT count(*) = 2 FROM pg_stat_activity WHERE wait_event LIKE 'WaitForWal%'"
+) or die "Timed out waiting for arc_standby waiters to block";
+
+# Resume replay. The startup process should wake the STANDBY_WRITE and
+# STANDBY_FLUSH waiters as it replays past arc_target_lsn2.
+my $arc_log_offset = -s $arc_standby->logfile;
+$arc_standby->safe_psql('postgres', "SELECT pg_wal_replay_resume()");
+
+# Wait for both sessions to complete.
+$arc_standby->wait_for_log(qr/write_arc_done/, $arc_log_offset);
+$arc_standby->wait_for_log(qr/flush_arc_done/, $arc_log_offset);
+
+$arc_write_session->quit;
+$arc_flush_session->quit;
+
+ok(1,
+ "standby_write/standby_flush waiters woken by replay on archive-only standby"
+);
+
$arc_standby->stop;
$arc_primary->stop;
--
2.39.5 (Apple Git-154)