Fix recovery conflict canceling the startup progress timeout

From: Aidar Imamov <imamovaj22(at)gmail(dot)com>
To: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Subject: Fix recovery conflict canceling the startup progress timeout
Date: 2026-10-26 13:21:47
Message-ID: 1D46953B-2FE9-4611-9B46-FAE91184AE99@gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

This came up while reviewing the startup progress timeout thread from
Fujii [1]. It is a separate, pre-existing thing, so following a
suggestion in that thread I am splitting it out into its own thread and
commitfest entry. The patch is attached.

The problem: a single recovery conflict stops "redo in progress"
messages for the rest of WAL replay, even though replay continues
normally.

ResolveRecoveryConflictWithLock() and
ResolveRecoveryConflictWithBufferPin() both set up their own timeouts
(STANDBY_LOCK_TIMEOUT / STANDBY_TIMEOUT and STANDBY_DEADLOCK_TIMEOUT),
wait, and then clean up with:

disable_all_timeouts(false);

That clears every timeout the process has, not only the ones these
functions enabled. The comment above it says the startup process is
assumed not to have any other outstanding timeouts, which was true when
the calls were written. It stopped being true in 15, when
STARTUP_PROGRESS_TIMEOUT was added by 9ce346eabf3.

begin_startup_progress_phase() is called once, right before the redo
loop. If a conflict happens later, the progress timeout is canceled
along with the conflict timeouts and nothing ever re-arms it, so
ereport_startup_progress() never fires again.

Where it is visible: only when progress is actually reported and clients
can run queries at the same time. PITR with
recovery.signal and hot_standby=on, after consistency is reached. That
is exactly the setup where people connect to check the data before
letting recovery finish, and where a long SELECT can block replay.

I reproduced it locally with recovery.signal, hot_standby=on, a slow
restore_command with a read-only query holding
ACCESS SHARE on a table that the WAL stream takes ACCESS EXCLUSIVE on:

no conflict: 22 "redo in progress" messages, up to "redo done"
one conflict: 7 messages, then silence for the last time of redo

Redo itself completes and the data is correct; only the log messages
disappear. But someone watching a long recovery sees the progress stop
and can easily conclude it is stuck.

The patch cancels exactly the timeouts each function enables:

ResolveRecoveryConflictWithLock():
disable_timeout(STANDBY_LOCK_TIMEOUT, false);
disable_timeout(STANDBY_DEADLOCK_TIMEOUT, false);

ResolveRecoveryConflictWithBufferPin():
disable_timeout(STANDBY_TIMEOUT, false);
disable_timeout(STANDBY_DEADLOCK_TIMEOUT, false);

This looks like a backpatch candidate to 15 and later, since the bug is
there from the moment the progress timeout exists.

[1] https://www.postgresql.org/message-id/flat/CAHGQGwFJixYTymPZ6dwOO-G0VdjO1AgZTma6CtJgc0Ptxfz3BA(at)mail(dot)gmail(dot)com

Regards,
Aidar Imamov

Attachment Content-Type Size
v1-Fix-recovery-conflict-canceling-the-startup-progress.patch application/octet-stream 2.4 KB

Browse pgsql-hackers by date

  From Date Subject
Next Message Greg Burd 2026-10-26 13:31:58 Re: Fix var_eq_const: sum selectivity of all matching MCV entries instead of stopping at first match
Previous Message Jeevan Chalke 2026-10-26 13:05:52 PL/pgSQL: fix validation of anycompatiblemultirange