diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index ece09699ef..9492a3c6b9 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -25344,7 +25344,24 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup()); boolean - Returns true if recovery is paused. + Returns true if recovery pause is requested. + + + + + + + pg_get_wal_replay_pause_state + + pg_get_wal_replay_pause_state () + text + + + Returns recovery pause state. The return values are + not paused if pause is not requested, + pause requested if pause is requested but recovery is + not yet paused and, paused if the recovery is + actually paused. @@ -25383,10 +25400,15 @@ postgres=# SELECT * FROM pg_walfile_name_offset(pg_stop_backup()); void - Pauses recovery. While recovery is paused, no further database - changes are applied. If hot standby is active, all new queries will - see the same consistent snapshot of the database, and no further query - conflicts will be generated until recovery is resumed. + Request to pause recovery. A request doesn't mean that recovery stops + right away. If you want a guarantee that recovery is actually paused, + you need to check for the recovery pause state returned by + pg_get_wal_replay_pause_state(). Note that + pg_is_wal_replay_paused() returns whether a request + is made. While recovery is paused, no further database changes are applied. + If hot standby is active, all new queries will see the same consistent + snapshot of the database, and no further query conflicts will be generated + until recovery is resumed. This function is restricted to superusers by default, but other users diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 18af3d4120..e04250f4e9 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -722,8 +722,8 @@ typedef struct XLogCtlData * only relevant for replication or archive recovery */ TimestampTz currentChunkStartTime; - /* Are we requested to pause recovery? */ - bool recoveryPause; + /* Recovery pause state */ + RecoveryPauseState recoveryPauseState; /* * lastFpwDisableRecPtr points to the start of the last replayed @@ -895,6 +895,7 @@ static void validateRecoveryParameters(void); static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog); static bool recoveryStopsBefore(XLogReaderState *record); static bool recoveryStopsAfter(XLogReaderState *record); +static void ConfirmRecoveryPaused(void); static void recoveryPausesHere(bool endOfRecovery); static bool recoveryApplyDelay(XLogReaderState *record); static void SetLatestXTime(TimestampTz xtime); @@ -6034,7 +6035,7 @@ recoveryStopsAfter(XLogReaderState *record) } /* - * Wait until shared recoveryPause flag is cleared. + * Wait until shared recoveryPauseState is set to RECOVERY_NOT_PAUSED. * * endOfRecovery is true if the recovery target is reached and * the paused state starts at the end of recovery because of @@ -6064,34 +6065,72 @@ recoveryPausesHere(bool endOfRecovery) (errmsg("recovery has paused"), errhint("Execute pg_wal_replay_resume() to continue."))); - while (RecoveryIsPaused()) + /* loop until recoveryPauseState is set to RECOVERY_NOT_PAUSED */ + while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED) { HandleStartupProcInterrupts(); if (CheckForStandbyTrigger()) return; pgstat_report_wait_start(WAIT_EVENT_RECOVERY_PAUSE); + + /* + * If recovery pause is requested then set it paused. While we are in + * the loop, user might resume and pause again so set this every time. + */ + ConfirmRecoveryPaused(); + pg_usleep(1000000L); /* 1000 ms */ pgstat_report_wait_end(); } } -bool -RecoveryIsPaused(void) +/* + * Get the current state of the recovery pause request. + */ +RecoveryPauseState +GetRecoveryPauseState(void) { - bool recoveryPause; + RecoveryPauseState state; SpinLockAcquire(&XLogCtl->info_lck); - recoveryPause = XLogCtl->recoveryPause; + state = XLogCtl->recoveryPauseState; SpinLockRelease(&XLogCtl->info_lck); - return recoveryPause; + return state; } +/* + * Set the recovery pause state. + * + * If recovery pause is requested then sets the recovery pause state to + * 'pause requested' if it is not already 'paused'. Otherwise, sets it + * to 'not paused' to resume the recovery. The recovery pause will be + * confirmed by the ConfirmRecoveryPaused. + */ void SetRecoveryPause(bool recoveryPause) { SpinLockAcquire(&XLogCtl->info_lck); - XLogCtl->recoveryPause = recoveryPause; + + if (!recoveryPause) + XLogCtl->recoveryPauseState = RECOVERY_NOT_PAUSED; + else if (XLogCtl->recoveryPauseState == RECOVERY_NOT_PAUSED) + XLogCtl->recoveryPauseState = RECOVERY_PAUSE_REQUESTED; + + SpinLockRelease(&XLogCtl->info_lck); +} + +/* + * Confirm the recovery pause by setting the recovery pause state to + * RECOVERY_PAUSED. + */ +static void +ConfirmRecoveryPaused(void) +{ + /* If recovery pause is requested then set it paused */ + SpinLockAcquire(&XLogCtl->info_lck); + if (XLogCtl->recoveryPauseState == RECOVERY_PAUSE_REQUESTED) + XLogCtl->recoveryPauseState = RECOVERY_PAUSED; SpinLockRelease(&XLogCtl->info_lck); } @@ -6292,7 +6331,7 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue errdetail("If recovery is unpaused, the server will shut down."), errhint("You can then restart the server after making the necessary configuration changes."))); - while (RecoveryIsPaused()) + while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED) { HandleStartupProcInterrupts(); @@ -6311,6 +6350,13 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue warned_for_promote = true; } + /* + * If recovery pause is requested then set it paused. While we + * are in the loop, user might resume and pause again so set + * this every time. + */ + ConfirmRecoveryPaused(); + pgstat_report_wait_start(WAIT_EVENT_RECOVERY_PAUSE); pg_usleep(1000000L); /* 1000 ms */ pgstat_report_wait_end(); @@ -7205,7 +7251,7 @@ StartupXLOG(void) XLogCtl->lastReplayedTLI = XLogCtl->replayEndTLI; XLogCtl->recoveryLastXTime = 0; XLogCtl->currentChunkStartTime = 0; - XLogCtl->recoveryPause = false; + XLogCtl->recoveryPauseState = RECOVERY_NOT_PAUSED; SpinLockRelease(&XLogCtl->info_lck); /* Also ensure XLogReceiptTime has a sane value */ @@ -7309,7 +7355,8 @@ StartupXLOG(void) * otherwise would is a minor issue, so it doesn't seem worth * adding another spinlock cycle to prevent that. */ - if (((volatile XLogCtlData *) XLogCtl)->recoveryPause) + if (((volatile XLogCtlData *) XLogCtl)->recoveryPauseState != + RECOVERY_NOT_PAUSED) recoveryPausesHere(false); /* @@ -7334,7 +7381,8 @@ StartupXLOG(void) * here otherwise pausing during the delay-wait wouldn't * work. */ - if (((volatile XLogCtlData *) XLogCtl)->recoveryPause) + if (((volatile XLogCtlData *) XLogCtl)->recoveryPauseState != + RECOVERY_NOT_PAUSED) recoveryPausesHere(false); } @@ -12656,6 +12704,14 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, elog(ERROR, "unexpected WAL source %d", currentSource); } + /* + * Check for recovery pause here so that we can confirm more quickly + * that a requested pause has actually taken effect. + */ + if (((volatile XLogCtlData *) XLogCtl)->recoveryPauseState != + RECOVERY_NOT_PAUSED) + recoveryPausesHere(false); + /* * This possibly-long loop needs to handle interrupts of startup * process. diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index d8c5bf6dc2..daa4a113b7 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -517,7 +517,7 @@ pg_walfile_name(PG_FUNCTION_ARGS) } /* - * pg_wal_replay_pause - pause recovery now + * pg_wal_replay_pause - Request to pause recovery * * Permission checking for this function is managed through the normal * GRANT system. @@ -540,6 +540,9 @@ pg_wal_replay_pause(PG_FUNCTION_ARGS) SetRecoveryPause(true); + /* wake up the recovery process so that it can process the pause request */ + WakeupRecovery(); + PG_RETURN_VOID(); } @@ -582,7 +585,45 @@ pg_is_wal_replay_paused(PG_FUNCTION_ARGS) errmsg("recovery is not in progress"), errhint("Recovery control functions can only be executed during recovery."))); - PG_RETURN_BOOL(RecoveryIsPaused()); + PG_RETURN_BOOL(GetRecoveryPauseState() != RECOVERY_NOT_PAUSED); +} + +/* + * pg_get_wal_replay_pause_state - Returns the recovery pause state. + * + * Returned values: + * + * 'not paused' - if pause is not requested + * 'pause requested' - if pause is requested but recovery is not yet paused + * 'paused' - if recovery is paused + */ +Datum +pg_get_wal_replay_pause_state(PG_FUNCTION_ARGS) +{ + char *statestr = NULL; + + if (!RecoveryInProgress()) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("recovery is not in progress"), + errhint("Recovery control functions can only be executed during recovery."))); + + /* get the recovery pause state */ + switch(GetRecoveryPauseState()) + { + case RECOVERY_NOT_PAUSED: + statestr = "not paused"; + break; + case RECOVERY_PAUSE_REQUESTED: + statestr = "pause requested"; + break; + case RECOVERY_PAUSED: + statestr = "paused"; + break; + } + + Assert(statestr != NULL); + PG_RETURN_TEXT_P(cstring_to_text(statestr)); } /* diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index 1e53d9d4ca..6d384d3ce6 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -175,6 +175,14 @@ typedef enum RecoveryState RECOVERY_STATE_DONE /* currently in production */ } RecoveryState; +/* Recovery pause states */ +typedef enum RecoveryPauseState +{ + RECOVERY_NOT_PAUSED, /* pause not requested */ + RECOVERY_PAUSE_REQUESTED, /* pause requested, but not yet paused */ + RECOVERY_PAUSED /* recovery is paused */ +} RecoveryPauseState; + extern PGDLLIMPORT int wal_level; /* Is WAL archiving enabled (always or only while server is running normally)? */ @@ -311,7 +319,7 @@ extern void GetXLogReceiptTime(TimestampTz *rtime, bool *fromStream); extern XLogRecPtr GetXLogReplayRecPtr(TimeLineID *replayTLI); extern XLogRecPtr GetXLogInsertRecPtr(void); extern XLogRecPtr GetXLogWriteRecPtr(void); -extern bool RecoveryIsPaused(void); +extern RecoveryPauseState GetRecoveryPauseState(void); extern void SetRecoveryPause(bool recoveryPause); extern TimestampTz GetLatestXTime(void); extern TimestampTz GetCurrentChunkReplayStartTime(void); diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index c7619f8cd3..61361a6bc9 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6234,6 +6234,10 @@ proname => 'pg_is_wal_replay_paused', provolatile => 'v', prorettype => 'bool', proargtypes => '', prosrc => 'pg_is_wal_replay_paused' }, +{ oid => '1137', descr => 'get wal replay pause state', + proname => 'pg_get_wal_replay_pause_state', provolatile => 'v', + prorettype => 'text', proargtypes => '', + prosrc => 'pg_get_wal_replay_pause_state' }, { oid => '2621', descr => 'reload configuration files', proname => 'pg_reload_conf', provolatile => 'v', prorettype => 'bool',