diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 834d0548cf..7912fb711d 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -28989,12 +28989,15 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset - Recovery Synchronization Procedure + Recovery Synchronization Procedure and Function - Procedure + Procedure or Function + + + Type Description @@ -29010,8 +29013,11 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset pg_wal_replay_wait ( target_lsn pg_lsn, - timeout bigint DEFAULT 0) - void + timeout bigint DEFAULT 0, + no_error bool DEFAULT false) + + + Procedure Waits until recovery replays target_lsn. @@ -29022,7 +29028,30 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset procedure waits until target_lsn is reached or the specified timeout has elapsed. On timeout, or if the server is promoted before - target_lsn is reached, an error is emitted. + target_lsn is reached, an error is emitted, + as soon as no_error is false. + If no_error is set to true, then the procedure + doesn't throw errors. The last result status could be read + with pg_wal_replay_wait_status. + + + + + + + pg_wal_replay_wait_status + + pg_wal_replay_wait_status () + text + + + Function + + + Returns the last result status for + pg_wal_replay_wait procedure. The possible + values are success, timeout, + and not in recovery. @@ -29044,7 +29073,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset pg_wal_replay_wait should be called on standby. If a user calls pg_wal_replay_wait on primary, it - will error out. However, if pg_wal_replay_wait is + will error out as soon as no_error is false. + However, if pg_wal_replay_wait is called on primary promoted from standby and target_lsn was already replayed, then pg_wal_replay_wait just exits immediately. @@ -29090,6 +29120,20 @@ postgres=# CALL pg_wal_replay_wait('0/306EE20', 100); ERROR: timed out while waiting for target LSN 0/306EE20 to be replayed; current replay LSN 0/306EA60 + The same example uses pg_wal_replay_wait with + no_error set to true. In this case, the result + status must be read with pg_wal_replay_wait_status. + + +postgres=# CALL pg_wal_replay_wait('0/306EE20', 100, true); +CALL +postgres=# SELECT pg_wal_replay_wait_status(); + pg_wal_replay_wait_status +--------------------------- + timeout +(1 row) + + diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index ddca78d371..bca1d39568 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -751,15 +751,18 @@ pg_promote(PG_FUNCTION_ARGS) PG_RETURN_BOOL(false); } +static WaitLSNResult lastWaitLSNResult = WAIT_LSN_RESULT_SUCCESS; + /* - * Waits until recovery replays the target LSN with optional timeout. + * Waits until recovery replays the target LSN with optional timeout. Unless + * 'no_error' provided throws an error on failure */ Datum pg_wal_replay_wait(PG_FUNCTION_ARGS) { XLogRecPtr target_lsn = PG_GETARG_LSN(0); int64 timeout = PG_GETARG_INT64(1); - WaitLSNResult result; + bool no_error = PG_GETARG_BOOL(2); if (timeout < 0) ereport(ERROR, @@ -800,13 +803,16 @@ pg_wal_replay_wait(PG_FUNCTION_ARGS) */ Assert(MyProc->xmin == InvalidTransactionId); - result = WaitForLSNReplay(target_lsn, timeout); + lastWaitLSNResult = WaitForLSNReplay(target_lsn, timeout); + + if (no_error) + PG_RETURN_VOID(); /* * Process the result of WaitForLSNReplay(). Throw appropriate error if * needed. */ - switch (result) + switch (lastWaitLSNResult) { case WAIT_LSN_RESULT_SUCCESS: /* Nothing to do on success */ @@ -832,3 +838,27 @@ pg_wal_replay_wait(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +Datum +pg_wal_replay_wait_status(PG_FUNCTION_ARGS) +{ + const char *result_string = ""; + + /* Process the result of WaitForLSNReplay(). */ + switch (lastWaitLSNResult) + { + case WAIT_LSN_RESULT_SUCCESS: + result_string = "success"; + break; + + case WAIT_LSN_RESULT_TIMEOUT: + result_string = "timeout"; + break; + + case WAIT_LSN_RESULT_NOT_IN_RECOVERY: + result_string = "not in recovery"; + break; + } + + PG_RETURN_TEXT_P(cstring_to_text(result_string)); +} diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c index 58fb10aa5a..8860a9c73d 100644 --- a/src/backend/access/transam/xlogwait.c +++ b/src/backend/access/transam/xlogwait.c @@ -2,7 +2,8 @@ * * xlogwait.c * Implements waiting for the given replay LSN, which is used in - * CALL pg_wal_replay_wait(target_lsn pg_lsn, timeout float8). + * CALL pg_wal_replay_wait(target_lsn pg_lsn, + * timeout float8, no_error bool). * * Copyright (c) 2024, PostgreSQL Global Development Group * diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 9c223edfac..20d3b9b73f 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -414,7 +414,9 @@ CREATE OR REPLACE FUNCTION json_populate_recordset(base anyelement, from_json json, use_json_as_text boolean DEFAULT false) RETURNS SETOF anyelement LANGUAGE internal STABLE ROWS 100 AS 'json_populate_recordset' PARALLEL SAFE; -CREATE OR REPLACE PROCEDURE pg_wal_replay_wait(target_lsn pg_lsn, timeout int8 DEFAULT 0) +CREATE OR REPLACE PROCEDURE pg_wal_replay_wait(target_lsn pg_lsn, + timeout int8 DEFAULT 0, + no_error bool DEFAULT false) LANGUAGE internal AS 'pg_wal_replay_wait'; CREATE OR REPLACE FUNCTION pg_logical_slot_get_changes( diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 391bf04bf5..8e0d93239f 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202410222 +#define CATALOG_VERSION_NO 202410241 #endif diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6297b7c679..90010d32a8 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6665,8 +6665,13 @@ { oid => '8593', descr => 'wait for the target LSN to be replayed on standby with an optional timeout', proname => 'pg_wal_replay_wait', prokind => 'p', prorettype => 'void', - proargtypes => 'pg_lsn int8', proargnames => '{target_lsn,timeout}', + proargtypes => 'pg_lsn int8 bool', proargnames => '{target_lsn,timeout,no_error}', prosrc => 'pg_wal_replay_wait' }, +{ oid => '8594', + descr => 'the last result for pg_wal_replay_wait()', + proname => 'pg_wal_replay_wait_status', prorettype => 'text', + proargtypes => '', + prosrc => 'pg_wal_replay_wait_status' }, { oid => '6224', descr => 'get resource managers loaded in system', proname => 'pg_get_wal_resource_managers', prorows => '50', proretset => 't', diff --git a/src/test/recovery/t/043_wal_replay_wait.pl b/src/test/recovery/t/043_wal_replay_wait.pl index cf77a9eec7..5857b94371 100644 --- a/src/test/recovery/t/043_wal_replay_wait.pl +++ b/src/test/recovery/t/043_wal_replay_wait.pl @@ -77,6 +77,20 @@ $node_standby->psql( ok( $stderr =~ /timed out while waiting for target LSN/, "get timeout on waiting for unreachable LSN"); +$output = $node_standby->safe_psql( + 'postgres', qq[ + CALL pg_wal_replay_wait('${lsn2}', 10, true); + SELECT pg_wal_replay_wait_status();]); +ok( $output eq "success", + "pg_wal_replay_wait_status() returns correct status after successful waiting" +); +$output = $node_standby->safe_psql( + 'postgres', qq[ + CALL pg_wal_replay_wait('${lsn3}', 10, true); + SELECT pg_wal_replay_wait_status();]); +ok($output eq "timeout", + "pg_wal_replay_wait_status() returns correct status after timeout"); + # 4. Check that pg_wal_replay_wait() triggers an error if called on primary, # within another function, or inside a transaction with an isolation level # higher than READ COMMITTED. @@ -193,6 +207,14 @@ $node_standby->safe_psql('postgres', "CALL pg_wal_replay_wait('${lsn5}');"); ok(1, 'wait for already replayed LSN exits immediately even after promotion'); +$output = $node_standby->safe_psql( + 'postgres', qq[ + CALL pg_wal_replay_wait('${lsn4}', 10, true); + SELECT pg_wal_replay_wait_status();]); +ok( $output eq "not in recovery", + "pg_wal_replay_wait_status() returns correct status after standby promotion" +); + $node_standby->stop; $node_primary->stop;