Hi all, We have observed an issue where pg_promote() returns false and issues a timeout warning prematurely, even if the standby server is successfully promoted later within the specified timeout period.
Problem Description The current implementation of pg_promote() calculates a fixed number of loop iterations based on the timeout value, assuming each loop waits exactly 100 ms for the backend latch. However, if the backend receives an unrelated signal (e.g., from client_connection_check_interval), it wakes up early. These repeated, unrelated wakeups cause the loop counter to deplete much faster than intended, leading to a premature timeout. Reproduction Set up a standby server while modifying pg_promote not to write the promote file to block the promotion. And by setting client_connection_check_interval = 1, we can consistently trigger a premature timeout. In the example below, a 10-second timeout expires in roughly 107 ms: postgres=# set client_connection_check_interval=1; SET postgres=# \timing Timing is on. postgres=# select pg_promote(true, 10); WARNING: server did not promote within 10 seconds ┌────────────┐ │ pg_promote │ ├────────────┤ │ f │ └────────────┘ (1 row) Time: 107.783 ms Proposed Fix The attached patch modifies the logic to loop based on the actual elapsed time rather than a fixed number of iterations. This ensures that pg_promote() respects the specified timeout regardless of how many times the backend latch is signaled. After applying the patch, the timeout behaves as expected: postgres=# set client_connection_check_interval=1; SET postgres=# \timing Timing is on. postgres=# select pg_promote(true, 10); WARNING: server did not promote within 10 seconds ┌────────────┐ │ pg_promote │ ├────────────┤ │ f │ └────────────┘ (1 row) Time: 10000.865 ms (00:10.001) We would like to submit this patch for the community's consideration. Best regards Robert Pang Google
From 2075aecdcc6a70fea1b33c9e90878e90c2a61612 Mon Sep 17 00:00:00 2001 From: Robert Pang <[email protected]> Date: Tue, 10 Mar 2026 21:11:49 -0700 Subject: [PATCH] Fix premature timeout in pg_promote() caused by signal interruptions Previously, pg_promote() looped a fixed number of times calculated from the specified timeout and waited 100 ms on the backend latch per iteration for standby promotion. However, unrelated signals to the backend could set the latch and wake up the backend early, resulting in a wait time significantly shorter than the specified timeout if the signals happened frequently. This commit refines the logic to track actual elapsed time. By looping until the requested duration has truly passed, pg_promote() now ensures that the wait does not end prematurely due to signals. Author: Robert Pang --- src/backend/access/transam/xlogfuncs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index ecb3c8c0820..6137af2d35e 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -690,7 +690,7 @@ pg_promote(PG_FUNCTION_ARGS) bool wait = PG_GETARG_BOOL(0); int wait_seconds = PG_GETARG_INT32(1); FILE *promote_file; - int i; + TimestampTz end_time; if (!RecoveryInProgress()) ereport(ERROR, @@ -731,8 +731,8 @@ pg_promote(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); /* wait for the amount of time wanted until promotion */ -#define WAITS_PER_SECOND 10 - for (i = 0; i < WAITS_PER_SECOND * wait_seconds; i++) + end_time = GetCurrentTimestamp() + wait_seconds * 1000000L; + while (GetCurrentTimestamp() < end_time) { int rc; @@ -745,7 +745,7 @@ pg_promote(PG_FUNCTION_ARGS) rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, - 1000L / WAITS_PER_SECOND, + 100L, WAIT_EVENT_PROMOTE); /* -- 2.53.0.473.g4a7958ca14-goog
