1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00

Fix remote_apply waits with wal_receiver_status_interval set to zero

With synchronous_commit = remote_apply, a commit could remain blocked even
after the synchronous standby had applied it when
wal_receiver_status_interval was set to zero. With both
wal_sender_timeout and wal_receiver_timeout disabled, the wait could last
indefinitely.

Commit 400a790a48 introduced this regression while avoiding unnecessary
duplicate standby status replies. It changed apply notifications from forced
replies to replies that check for WAL position changes. However,
XLogWalRcvSendReply() still returned early for non-forced replies when
wal_receiver_status_interval was zero, before checking the apply position.

Fix this by allowing apply-notification requests to bypass the early return
while preserving the existing checks that suppress unnecessary duplicate
replies.

Author: Vaibhav Dalvi <vaibhav.dalvi@enterprisedb.com>
Reviewed-by: Ashutosh Sharma <ashu.coek88@gmail.com>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Discussion: https://postgr.es/m/CA+vB=AHKUhc9GCDwoLr=SshTBCQG09kp8TEXew50UtD5rYqowg@mail.gmail.com
Backpatch-through: 19
This commit is contained in:
Fujii Masao
2026-10-07 19:09:23 +09:00
parent e40b509c08
commit cc33773482
3 changed files with 75 additions and 5 deletions

View File

@@ -1196,8 +1196,8 @@ XLogWalRcvClose(XLogRecPtr recptr, TimeLineID tli)
* The message is sent if 'force' is set, if enough time has passed since the
* last update to reach wal_receiver_status_interval, or if WAL locations have
* advanced since the previous status update. If wal_receiver_status_interval
* is disabled and 'force' is false, this function does nothing. Set 'force' to
* send the message unconditionally.
* is disabled and neither 'force' nor 'checkApply' is set, this function does
* nothing. Set 'force' to send the message unconditionally.
*
* Whether WAL locations are considered "advanced" depends on 'checkApply'.
* If 'checkApply' is false, only the write and flush locations are checked.
@@ -1222,10 +1222,11 @@ XLogWalRcvSendReply(bool force, bool requestReply, bool checkApply)
TimestampTz now;
/*
* If the user doesn't want status to be reported to the primary, be sure
* to exit before doing anything at all.
* If status reporting is disabled, skip non-forced replies unless the
* startup process has requested an apply notification. Backends waiting
* with synchronous_commit = remote_apply depend on these notifications.
*/
if (!force && wal_receiver_status_interval <= 0)
if (!force && !checkApply && wal_receiver_status_interval <= 0)
return;
/* Get current timestamp. */

View File

@@ -67,6 +67,7 @@ tests += {
't/056_standby_snapshot_export.pl',
't/057_snapshot_commit_race.pl',
't/058_shutdown_crash_restart.pl',
't/059_remote_apply_status_interval.pl',
],
},
}

View File

@@ -0,0 +1,68 @@
# Copyright (c) 2026, PostgreSQL Global Development Group
# Check that a commit with synchronous_commit = remote_apply completes when
# the standby has wal_receiver_status_interval = 0. Periodic status updates
# are disabled then, but the walreceiver must still send the apply reply
# requested by the startup process.
#
# Disable timeout-driven keepalives from the walsender and pings from the
# walreceiver, so that they cannot trigger a status reply that would mask
# a missing apply notification.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use Test::More;
my $primary = PostgreSQL::Test::Cluster->new('primary');
$primary->init(allows_streaming => 1);
# Configure synchronous replication at startup, so that there is no window
# where the standby is reported as synchronous before the backends know that
# they have to wait. Use synchronous_commit = local so that the setup does not
# wait for the standby, which is not available yet.
$primary->append_conf(
'postgresql.conf', qq(
wal_sender_timeout = 0
synchronous_standby_names = '*'
synchronous_commit = local
));
$primary->start;
$primary->safe_psql('postgres', 'CREATE TABLE t (a int)');
$primary->backup('bkp');
my $standby = PostgreSQL::Test::Cluster->new('standby');
$standby->init_from_backup($primary, 'bkp', has_streaming => 1);
$standby->append_conf(
'postgresql.conf', qq(
wal_receiver_status_interval = 0
wal_receiver_timeout = 0
hot_standby_feedback = off
));
$standby->start;
$primary->poll_query_until(
'postgres',
"SELECT count(*) = 1 FROM pg_stat_replication
WHERE state = 'streaming' AND sync_state = 'sync'
AND flush_lsn IS NOT NULL"
) or die "timed out waiting for standby to become synchronous";
# The commit must complete even with periodic status updates disabled.
# If the standby fails to report that the commit has been applied,
# query_safe() fails when the session timeout expires.
my $bg = $primary->background_psql('postgres');
$bg->query_safe('SET synchronous_commit = remote_apply');
$bg->query_safe('INSERT INTO t VALUES (1)');
pass('remote_apply commit completes with wal_receiver_status_interval = 0');
is($standby->safe_psql('postgres', 'SELECT count(*) FROM t'),
'1', 'remote_apply commit is visible on standby');
$bg->quit;
$standby->stop;
$primary->stop;
done_testing();