Add 'no_error' argument to pg_wal_replay_wait()
authorAlexander Korotkov
Thu, 24 Oct 2024 11:40:23 +0000 (14:40 +0300)
committerAlexander Korotkov
Thu, 24 Oct 2024 12:02:21 +0000 (15:02 +0300)
This argument allow skipping throwing an error.  Instead, the result status
can be obtained using pg_wal_replay_wait_status() function.

Catversion is bumped.

Reported-by: Michael Paquier
Discussion: https://postgr.es/m/ZtUF17gF0pNpwZDI%40paquier.xyz
Reviewed-by: Pavel Borisov
doc/src/sgml/func.sgml
src/backend/access/transam/xlogfuncs.c
src/backend/access/transam/xlogwait.c
src/backend/catalog/system_functions.sql
src/include/catalog/catversion.h
src/include/catalog/pg_proc.dat
src/test/recovery/t/043_wal_replay_wait.pl

index 834d0548cfca338ae6f5d8ad1bddf5f4c091efe5..7912fb711d37838ac32f73d0d0453274e2c418e4 100644 (file)
@@ -28989,12 +28989,15 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
    
 
    
-    Recovery Synchronization Procedure
+    Recovery Synchronization Procedure<span class="marked"> and Function</span>
     
      
       
        
-        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 successtimeout,
+        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)
+    
+
    
 
    
index ddca78d371732b7102b150cf43f2a75e5e4d4005..bca1d39568359e307160b8ab8b8e44cf1472b978 100644 (file)
@@ -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));
+}
index 58fb10aa5a88e9526a2a7ac442435f7642eb6aad..8860a9c73da28adaef025f63bb22c37bc00beec4 100644 (file)
@@ -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
  *
index 9c223edfac97c339244dc3c25bc069c4084fec96..20d3b9b73fdae14070a34c7e813a587ca4c3e998 100644 (file)
@@ -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(
index 391bf04bf5d4d778f04a164749a0a4bb65f256b9..8e0d93239fed62302b47c7124ba417151c32465e 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202410222
+#define CATALOG_VERSION_NO 202410241
 
 #endif
index 6297b7c679dfc6c02fa403b6dc9e767f471a19b6..90010d32a85455134d33044b0ac4abfb4105ddef 100644 (file)
 { 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',
index cf77a9eec70a6ab32e6af9b48068fd001ef11792..5857b943711c1f983aa0a1e29a5feadd60069708 100644 (file)
@@ -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;