From af3d86506e5dd20b84c05c54f66d57fb480f1904 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 19 Sep 2024 15:34:18 +0300 Subject: [PATCH v2 2/2] Implement pg_wal_replay_wait_no_error() And function pg_wal_replay_wait_status() to get the last status. --- src/backend/access/transam/xlogfuncs.c | 93 ++++++++++++++++++---- src/backend/catalog/system_functions.sql | 5 ++ src/include/catalog/pg_proc.dat | 11 +++ src/test/recovery/t/043_wal_replay_wait.pl | 12 +++ 4 files changed, 105 insertions(+), 16 deletions(-) diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 74b493e437e..6602507f452 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -752,20 +752,11 @@ pg_promote(PG_FUNCTION_ARGS) } /* - * Waits until recovery replays the target LSN with optional timeout. + * Prepare for waiting for LSN replay. */ -Datum -pg_wal_replay_wait(PG_FUNCTION_ARGS) +static void +pg_wal_replay_wait_prepare(const char *funcname) { - XLogRecPtr target_lsn = PG_GETARG_LSN(0); - int64 timeout = PG_GETARG_INT64(1); - WaitLSNResult result; - - if (timeout < 0) - ereport(ERROR, - (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("\"timeout\" must not be negative"))); - /* * We are going to wait for the LSN replay. We should first care that we * don't hold a snapshot and correspondingly our MyProc->xmin is invalid. @@ -791,22 +782,42 @@ pg_wal_replay_wait(PG_FUNCTION_ARGS) if (GetOldestSnapshot()) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("pg_wal_replay_wait() must be only called without an active or registered snapshot"), - errdetail("Make sure pg_wal_replay_wait() isn't called within a transaction with an isolation level higher than READ COMMITTED, another procedure, or a function."))); + errmsg("%s must be only called without an active or registered snapshot", funcname), + errdetail("Make sure %s isn't called within a transaction with an isolation level higher than READ COMMITTED, another procedure, or a function.", funcname))); /* * As the result we should hold no snapshot, and correspondingly our xmin * should be unset. */ Assert(MyProc->xmin == InvalidTransactionId); +} - result = WaitForLSNReplay(target_lsn, timeout); +static WaitLSNResult lastWaitLSNResult = WaitLSNResultSuccess; + +/* + * Waits until recovery replays the target LSN with optional timeout. Throw + * 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); + + if (timeout < 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"timeout\" must not be negative"))); + + pg_wal_replay_wait_prepare("pg_wal_replay_wait()"); + + lastWaitLSNResult = WaitForLSNReplay(target_lsn, timeout); /* * Process the result of WaitForLSNReplay(). Throw appropriate error if * needed. */ - switch (result) + switch (lastWaitLSNResult) { case WaitLSNResultSuccess: /* Nothing to do on success */ @@ -839,3 +850,53 @@ pg_wal_replay_wait(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +/* + * Waits until recovery replays the target LSN with optional timeout. Return + * the waiting result as a text. + */ +Datum +pg_wal_replay_wait_no_error(PG_FUNCTION_ARGS) +{ + XLogRecPtr target_lsn = PG_GETARG_LSN(0); + int64 timeout = PG_GETARG_INT64(1); + + if (timeout < 0) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("\"timeout\" must not be negative"))); + + pg_wal_replay_wait_prepare("pg_wal_replay_wait_status()"); + + lastWaitLSNResult = WaitForLSNReplay(target_lsn, timeout); + + return (Datum) 0; +} + +Datum +pg_wal_replay_wait_status(PG_FUNCTION_ARGS) +{ + const char *result_string = ""; + + /* Process the result of WaitForLSNReplay(). */ + switch (lastWaitLSNResult) + { + case WaitLSNResultSuccess: + result_string = "success"; + break; + + case WaitLSNResultTimeout: + result_string = "timeout"; + break; + + case WaitLSNResultNotInRecovery: + result_string = "not in recovery"; + break; + + case WaitLSNResultPromotedConcurrently: + result_string = "promoted concurrently"; + break; + } + + PG_RETURN_TEXT_P(cstring_to_text(result_string)); +} diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index b0d0de051e7..ed092b748ef 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -417,6 +417,11 @@ CREATE OR REPLACE FUNCTION CREATE OR REPLACE PROCEDURE pg_wal_replay_wait(target_lsn pg_lsn, timeout int8 DEFAULT 0) LANGUAGE internal AS 'pg_wal_replay_wait'; +CREATE OR REPLACE PROCEDURE pg_wal_replay_wait_status(OUT status text, + target_lsn pg_lsn, + timeout int8 DEFAULT 0) + LANGUAGE internal AS 'pg_wal_replay_wait_status'; + CREATE OR REPLACE FUNCTION pg_logical_slot_get_changes( IN slot_name name, IN upto_lsn pg_lsn, IN upto_nchanges int, VARIADIC options text[] DEFAULT '{}', OUT lsn pg_lsn, OUT xid xid, OUT data text) diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 43f608d7a0a..36024a6e2c9 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6647,6 +6647,17 @@ proname => 'pg_wal_replay_wait', prokind => 'p', prorettype => 'void', proargtypes => 'pg_lsn int8', proargnames => '{target_lsn,timeout}', prosrc => 'pg_wal_replay_wait' }, +{ oid => '226', + descr => 'wait for the target LSN to be replayed on standby with an optional timeout without error throwing', + proname => 'pg_wal_replay_wait_no_error', prokind => 'p', prorettype => 'void', + proargtypes => 'pg_lsn int8', proargmodes => '{i,i}', + proargnames => '{target_lsn,timeout}', + prosrc => 'pg_wal_replay_wait_no_error' }, +{ oid => '388', + descr => 'return twait for the target LSN to be replayed on standby with an optional timeout and returning the waiting result', + 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 024f1fe6488..d5bf48895fa 100644 --- a/src/test/recovery/t/043_wal_replay_wait.pl +++ b/src/test/recovery/t/043_wal_replay_wait.pl @@ -77,6 +77,18 @@ $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_no_error('${lsn2}', 10); + SELECT pg_wal_replay_wait_status();]); +ok( $output == "success", + "pg_wal_replay_wait_status() returns correct status after successful waiting"); +$output = $node_standby->psql( + 'postgres', qq[ + CALL pg_wal_replay_wait_no_error('${lsn2}', 10); + SELECT pg_wal_replay_wait_status();]); +ok( $output == "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. -- 2.39.5 (Apple Git-154)