Hello all
I want propose patch to make possible change primary_conninfo,
primary_slot_name, restore_command and trigger_file in recovery.conf without
restart postgresql.
Startup process will reread recovery.conf on SIGHUP.
My primary usecase is postgresql cluster with streaming replication. When we
lost current master and want promote new master we need restart all replicas
with new primary_conninfo.
Another useful cases from my work:
- switch wal streaming to another NIC
- change replication username (or password when .pgpass was not used)
When restarting, shared buffers are lost; some time are spent in recovery from
last restart point. But in some cases all we need is to change connection
string.
Changing restore_command may be useful for users too.
I did not find previous discussions.
I split readRecoveryCommandFile to two functions:
- readRecoveryCommandFile with reading and validating recovery.conf and avoid
side effects
- ProcessRecoveryCommandFile with other logic
Changing primary_conninfo or primary_slot_name will restart walreceiver (i do
not touch walreceiver source, i request standard restart)
trigger_file was not my primary target, but i not find any reason to decline
this change on reload (also did not find any tests with trigger_file, i add
one). I am not sure about all other settings like recovery_min_apply_delay,
archive_cleanup_command and etc, so i forbid these changes.
Patch to current master. Changes to the documentation and a few additional
tests are included.
PS: of course, i do not mean postgresql 11
regards, Sergei
diff --git a/doc/src/sgml/recovery-config.sgml b/doc/src/sgml/recovery-config.sgml
index 92825fd..0660938 100644
--- a/doc/src/sgml/recovery-config.sgml
+++ b/doc/src/sgml/recovery-config.sgml
@@ -14,7 +14,9 @@
<filename>recovery.conf</filename><indexterm><primary>recovery.conf</primary></indexterm>
file. They apply only for the duration of the
recovery. They must be reset for any subsequent recovery you wish to
- perform. They cannot be changed once recovery has begun.
+ perform. Excepts <varname>restore_command</varname>, <varname>primary_conninfo</varname>,
+ <varname>primary_slot_name</varname> and <varname>trigger_file</varname>
+ they cannot be changed once recovery has begun.
</para>
<para>
@@ -76,6 +78,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
database server shutdown) or an error by the shell (such as command
not found), then recovery will abort and the server will not start up.
</para>
+
+ <para>
+ Can be changed during recovery with configuration files reload.
+ </para>
</listitem>
</varlistentry>
@@ -410,6 +416,11 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This setting has no effect if <varname>standby_mode</varname> is <literal>off</literal>.
</para>
+ <para>
+ Can be changed during recovery with configuration files reload.
+ <application>walreceiver</application> will be restart after
+ <varname>primary_conninfo</varname> was changed.
+ </para>
</listitem>
</varlistentry>
<varlistentry id="primary-slot-name" xreflabel="primary_slot_name">
@@ -427,6 +438,11 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
This setting has no effect if <varname>primary_conninfo</varname> is not
set.
</para>
+ <para>
+ Can be changed during recovery with configuration files reload.
+ <application>walreceiver</application> will be restart after
+ <varname>primary_slot_name</varname> was changed.
+ </para>
</listitem>
</varlistentry>
<varlistentry id="trigger-file" xreflabel="trigger_file">
@@ -442,6 +458,9 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
the standby using <command>pg_ctl promote</command>.
This setting has no effect if <varname>standby_mode</varname> is <literal>off</literal>.
</para>
+ <para>
+ Can be changed during recovery with configuration files reload.
+ </para>
</listitem>
</varlistentry>
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c0923d9..55409fd 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -255,26 +255,39 @@ static bool restoredFromArchive = false;
static char *replay_image_masked = NULL;
static char *master_image_masked = NULL;
-/* options taken from recovery.conf for archive recovery */
-char *recoveryRestoreCommand = NULL;
-static char *recoveryEndCommand = NULL;
-static char *archiveCleanupCommand = NULL;
-static RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
-static bool recoveryTargetInclusive = true;
-static RecoveryTargetAction recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
-static TransactionId recoveryTargetXid;
-static TimestampTz recoveryTargetTime;
-static char *recoveryTargetName;
-static XLogRecPtr recoveryTargetLSN;
-static int recovery_min_apply_delay = 0;
+/* options taken from RECOVERY_COMMAND_FILE */
+typedef struct recoveryCommandData
+{
+ bool confExists; /* is RECOVERY_COMMAND_FILE present */
+
+ /* options for archive recovery */
+ char *recoveryRestoreCommand;
+ char *recoveryEndCommand;
+ char *archiveCleanupCommand;
+ RecoveryTargetType recoveryTarget;
+ bool recoveryTargetInclusive;
+ RecoveryTargetAction recoveryTargetAction;
+ TransactionId recoveryTargetXid;
+ TimestampTz recoveryTargetTime;
+ char *recoveryTargetName;
+ XLogRecPtr recoveryTargetLSN;
+ int recoveryMinApplyDelay;
+
+ /* options for XLOG streaming */
+ bool StandbyModeRequested;
+ char *PrimaryConnInfo;
+ char *PrimarySlotName;
+ char *TriggerFile;
+
+ /* recovery_target_timeline option handling */
+ bool recoveryTargetTimelineGiven;
+ TimeLineID recoveryTargetTimeline;
+} recoveryCommandData;
+static recoveryCommandData * recoveryConfig = NULL;
+
+/* calculated timestamp to wait recoveryMinApplyDelay */
static TimestampTz recoveryDelayUntilTime;
-/* options taken from recovery.conf for XLOG streaming */
-static bool StandbyModeRequested = false;
-static char *PrimaryConnInfo = NULL;
-static char *PrimarySlotName = NULL;
-static char *TriggerFile = NULL;
-
/* are we currently in standby mode? */
bool StandbyMode = false;
@@ -846,7 +859,7 @@ static bool holdingAllLocks = false;
static MemoryContext walDebugCxt = NULL;
#endif
-static void readRecoveryCommandFile(void);
+static bool readRecoveryCommandFile(bool isReload, recoveryCommandData * conf);
static void exitArchiveRecovery(TimeLineID endTLI, XLogRecPtr endOfLog);
static bool recoveryStopsBefore(XLogReaderState *record);
static bool recoveryStopsAfter(XLogReaderState *record);
@@ -4251,7 +4264,7 @@ ReadRecord(XLogReaderState *xlogreader, XLogRecPtr RecPtr, int emode,
ereport(DEBUG1,
(errmsg_internal("reached end of WAL in pg_wal, entering archive recovery")));
InArchiveRecovery = true;
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
StandbyMode = true;
/* initialize minRecoveryPoint to this record */
@@ -5215,80 +5228,138 @@ str_time(pg_time_t tnow)
}
/*
+ * Initialize default recoveryCommandData
+ */
+static recoveryCommandData *
+DefaultRecoveryCommandFile()
+{
+ recoveryCommandData *conf = palloc(sizeof(recoveryCommandData));
+
+ conf->confExists = false;
+ conf->recoveryRestoreCommand = NULL;
+ conf->recoveryEndCommand = NULL;
+ conf->archiveCleanupCommand = NULL;
+ conf->recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
+ conf->recoveryTargetTimeline = 0;
+ conf->recoveryTargetTimelineGiven = false;
+ conf->recoveryTarget = RECOVERY_TARGET_UNSET;
+ conf->recoveryTargetInclusive = true;
+ conf->StandbyModeRequested = false;
+ conf->PrimaryConnInfo = NULL;
+ conf->PrimarySlotName = NULL;
+ conf->TriggerFile = NULL;
+ conf->recoveryMinApplyDelay = 0;
+ return conf;
+}
+
+static void
+FreeRecoveryCommandFile(recoveryCommandData * conf)
+{
+ if (conf->recoveryRestoreCommand)
+ pfree(conf->recoveryRestoreCommand);
+ if (conf->recoveryEndCommand)
+ pfree(conf->recoveryEndCommand);
+ if (conf->archiveCleanupCommand)
+ pfree(conf->archiveCleanupCommand);
+ if (conf->PrimaryConnInfo)
+ pfree(conf->PrimaryConnInfo);
+ if (conf->PrimarySlotName)
+ pfree(conf->PrimarySlotName);
+ if (conf->TriggerFile)
+ pfree(conf->TriggerFile);
+ pfree(conf);
+}
+
+/*
* See if there is a recovery command file (recovery.conf), and if so
- * read in parameters for archive recovery and XLOG streaming.
+ * read it and populate given recoveryCommandData for further processing
*
* The file is parsed using the main configuration parser.
+ *
+ * When isReload is true errors will be reported without exit and
+ * function return false. Otherwise any error will abort startup process
*/
-static void
-readRecoveryCommandFile(void)
+static bool
+readRecoveryCommandFile(bool isReload, recoveryCommandData * conf)
{
FILE *fd;
- TimeLineID rtli = 0;
- bool rtliGiven = false;
ConfigVariable *item,
*head = NULL,
*tail = NULL;
bool recoveryTargetActionSet = false;
-
+ bool parseConfigFpSuccess = false;
+ bool configHasErrors = false;
+ int fatalReportLevel = (isReload ? WARNING : FATAL);
+ int errorReportLevel = (isReload ? LOG : ERROR);
fd = AllocateFile(RECOVERY_COMMAND_FILE, "r");
if (fd == NULL)
{
- if (errno == ENOENT)
- return; /* not there, so no archive recovery */
- ereport(FATAL,
+ if (errno == ENOENT && !isReload)
+ return true; /* not there, so no archive recovery on
+ * startup requested */
+ ereport(fatalReportLevel,
(errcode_for_file_access(),
errmsg("could not open recovery command file \"%s\": %m",
RECOVERY_COMMAND_FILE)));
+ return false;
}
- /*
- * Since we're asking ParseConfigFp() to report errors as FATAL, there's
- * no need to check the return value.
- */
- (void) ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, FATAL, &head, &tail);
+ conf->confExists = true;
+
+ parseConfigFpSuccess = ParseConfigFp(fd, RECOVERY_COMMAND_FILE, 0, fatalReportLevel, &head, &tail);
FreeFile(fd);
+ if (!parseConfigFpSuccess)
+ {
+ /* error in parsing was reported by ParseConfigFp */
+ FreeConfigVariables(head);
+ return false;
+ }
+
for (item = head; item; item = item->next)
{
if (strcmp(item->name, "restore_command") == 0)
{
- recoveryRestoreCommand = pstrdup(item->value);
+ conf->recoveryRestoreCommand = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("restore_command = '%s'",
- recoveryRestoreCommand)));
+ conf->recoveryRestoreCommand)));
}
else if (strcmp(item->name, "recovery_end_command") == 0)
{
- recoveryEndCommand = pstrdup(item->value);
+ conf->recoveryEndCommand = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("recovery_end_command = '%s'",
- recoveryEndCommand)));
+ conf->recoveryEndCommand)));
}
else if (strcmp(item->name, "archive_cleanup_command") == 0)
{
- archiveCleanupCommand = pstrdup(item->value);
+ conf->archiveCleanupCommand = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("archive_cleanup_command = '%s'",
- archiveCleanupCommand)));
+ conf->archiveCleanupCommand)));
}
else if (strcmp(item->name, "recovery_target_action") == 0)
{
if (strcmp(item->value, "pause") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
+ conf->recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
else if (strcmp(item->value, "promote") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE;
+ conf->recoveryTargetAction = RECOVERY_TARGET_ACTION_PROMOTE;
else if (strcmp(item->value, "shutdown") == 0)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
+ conf->recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
else
- ereport(ERROR,
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
"recovery_target_action",
item->value),
errhint("Valid values are \"pause\", \"promote\", and \"shutdown\".")));
+ configHasErrors = true;
+ continue;
+ }
ereport(DEBUG2,
(errmsg_internal("recovery_target_action = '%s'",
@@ -5298,22 +5369,23 @@ readRecoveryCommandFile(void)
}
else if (strcmp(item->name, "recovery_target_timeline") == 0)
{
- rtliGiven = true;
+ conf->recoveryTargetTimelineGiven = true;
if (strcmp(item->value, "latest") == 0)
- rtli = 0;
+ conf->recoveryTargetTimeline = 0;
else
{
errno = 0;
- rtli = (TimeLineID) strtoul(item->value, NULL, 0);
+ conf->recoveryTargetTimeline = (TimeLineID) strtoul(item->value, NULL, 0);
if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("recovery_target_timeline is not a valid number: \"%s\"",
item->value)));
}
- if (rtli)
+ if (conf->recoveryTargetTimeline)
ereport(DEBUG2,
- (errmsg_internal("recovery_target_timeline = %u", rtli)));
+ (errmsg_internal("recovery_target_timeline = %u",
+ conf->recoveryTargetTimeline)));
else
ereport(DEBUG2,
(errmsg_internal("recovery_target_timeline = latest")));
@@ -5321,21 +5393,23 @@ readRecoveryCommandFile(void)
else if (strcmp(item->name, "recovery_target_xid") == 0)
{
errno = 0;
- recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
+ conf->recoveryTargetXid = (TransactionId) strtoul(item->value, NULL, 0);
if (errno == EINVAL || errno == ERANGE)
- ereport(FATAL,
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("recovery_target_xid is not a valid number: \"%s\"",
item->value)));
+ configHasErrors = true;
+ continue;
+ }
ereport(DEBUG2,
(errmsg_internal("recovery_target_xid = %u",
- recoveryTargetXid)));
- recoveryTarget = RECOVERY_TARGET_XID;
+ conf->recoveryTargetXid)));
+ conf->recoveryTarget = RECOVERY_TARGET_XID;
}
else if (strcmp(item->name, "recovery_target_time") == 0)
{
- recoveryTarget = RECOVERY_TARGET_TIME;
-
if (strcmp(item->value, "epoch") == 0 ||
strcmp(item->value, "infinity") == 0 ||
strcmp(item->value, "-infinity") == 0 ||
@@ -5343,66 +5417,77 @@ readRecoveryCommandFile(void)
strcmp(item->value, "today") == 0 ||
strcmp(item->value, "tomorrow") == 0 ||
strcmp(item->value, "yesterday") == 0)
- ereport(FATAL,
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("recovery_target_time is not a valid timestamp: \"%s\"",
item->value)));
+ configHasErrors = true;
+ continue;
+ }
/*
* Convert the time string given by the user to TimestampTz form.
*/
- recoveryTargetTime =
+ conf->recoveryTargetTime =
DatumGetTimestampTz(DirectFunctionCall3(timestamptz_in,
CStringGetDatum(item->value),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1)));
+ conf->recoveryTarget = RECOVERY_TARGET_TIME;
ereport(DEBUG2,
(errmsg_internal("recovery_target_time = '%s'",
- timestamptz_to_str(recoveryTargetTime))));
+ timestamptz_to_str(conf->recoveryTargetTime))));
}
else if (strcmp(item->name, "recovery_target_name") == 0)
{
- recoveryTarget = RECOVERY_TARGET_NAME;
-
- recoveryTargetName = pstrdup(item->value);
- if (strlen(recoveryTargetName) >= MAXFNAMELEN)
- ereport(FATAL,
+ if (strlen(item->value) >= MAXFNAMELEN)
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("recovery_target_name is too long (maximum %d characters)",
MAXFNAMELEN - 1)));
+ configHasErrors = true;
+ continue;
+ }
+ conf->recoveryTarget = RECOVERY_TARGET_NAME;
+ conf->recoveryTargetName = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("recovery_target_name = '%s'",
- recoveryTargetName)));
+ conf->recoveryTargetName)));
}
else if (strcmp(item->name, "recovery_target_lsn") == 0)
{
- recoveryTarget = RECOVERY_TARGET_LSN;
+ conf->recoveryTarget = RECOVERY_TARGET_LSN;
/*
* Convert the LSN string given by the user to XLogRecPtr form.
*/
- recoveryTargetLSN =
+ conf->recoveryTargetLSN =
DatumGetLSN(DirectFunctionCall3(pg_lsn_in,
CStringGetDatum(item->value),
ObjectIdGetDatum(InvalidOid),
Int32GetDatum(-1)));
ereport(DEBUG2,
(errmsg_internal("recovery_target_lsn = '%X/%X'",
- (uint32) (recoveryTargetLSN >> 32),
- (uint32) recoveryTargetLSN)));
+ (uint32) (conf->recoveryTargetLSN >> 32),
+ (uint32) conf->recoveryTargetLSN)));
}
else if (strcmp(item->name, "recovery_target") == 0)
{
if (strcmp(item->value, "immediate") == 0)
- recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
+ conf->recoveryTarget = RECOVERY_TARGET_IMMEDIATE;
else
- ereport(ERROR,
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid value for recovery parameter \"%s\": \"%s\"",
"recovery_target",
item->value),
errhint("The only allowed value is \"immediate\".")));
+ configHasErrors = true;
+ }
ereport(DEBUG2,
(errmsg_internal("recovery_target = '%s'",
item->value)));
@@ -5412,74 +5497,94 @@ readRecoveryCommandFile(void)
/*
* does nothing if a recovery_target is not also set
*/
- if (!parse_bool(item->value, &recoveryTargetInclusive))
- ereport(ERROR,
+ if (!parse_bool(item->value, &conf->recoveryTargetInclusive))
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a Boolean value",
"recovery_target_inclusive")));
+ configHasErrors = true;
+ }
ereport(DEBUG2,
(errmsg_internal("recovery_target_inclusive = %s",
item->value)));
}
else if (strcmp(item->name, "standby_mode") == 0)
{
- if (!parse_bool(item->value, &StandbyModeRequested))
- ereport(ERROR,
+ if (!parse_bool(item->value, &conf->StandbyModeRequested))
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a Boolean value",
"standby_mode")));
+ configHasErrors = true;
+ }
ereport(DEBUG2,
(errmsg_internal("standby_mode = '%s'", item->value)));
}
else if (strcmp(item->name, "primary_conninfo") == 0)
{
- PrimaryConnInfo = pstrdup(item->value);
+ conf->PrimaryConnInfo = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("primary_conninfo = '%s'",
- PrimaryConnInfo)));
+ conf->PrimaryConnInfo)));
}
else if (strcmp(item->name, "primary_slot_name") == 0)
{
- ReplicationSlotValidateName(item->value, ERROR);
- PrimarySlotName = pstrdup(item->value);
- ereport(DEBUG2,
- (errmsg_internal("primary_slot_name = '%s'",
- PrimarySlotName)));
+ if (ReplicationSlotValidateName(item->value, fatalReportLevel))
+ {
+ conf->PrimarySlotName = pstrdup(item->value);
+ ereport(DEBUG2,
+ (errmsg_internal("primary_slot_name = '%s'",
+ conf->PrimarySlotName)));
+ }
+ else
+ {
+ configHasErrors = true;
+ }
}
else if (strcmp(item->name, "trigger_file") == 0)
{
- TriggerFile = pstrdup(item->value);
+ conf->TriggerFile = pstrdup(item->value);
ereport(DEBUG2,
(errmsg_internal("trigger_file = '%s'",
- TriggerFile)));
+ conf->TriggerFile)));
}
else if (strcmp(item->name, "recovery_min_apply_delay") == 0)
{
const char *hintmsg;
- if (!parse_int(item->value, &recovery_min_apply_delay, GUC_UNIT_MS,
- &hintmsg))
- ereport(ERROR,
+ if (!parse_int(item->value, &conf->recoveryMinApplyDelay,
+ GUC_UNIT_MS, &hintmsg))
+ {
+ ereport(errorReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("parameter \"%s\" requires a temporal value",
"recovery_min_apply_delay"),
hintmsg ? errhint("%s", _(hintmsg)) : 0));
+ configHasErrors = true;
+ }
ereport(DEBUG2,
(errmsg_internal("recovery_min_apply_delay = '%s'", item->value)));
}
else
- ereport(FATAL,
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("unrecognized recovery parameter \"%s\"",
item->name)));
+ configHasErrors = true;
+ }
}
+ FreeConfigVariables(head);
+
/*
* Check for compulsory parameters
*/
- if (StandbyModeRequested)
+ if (conf->StandbyModeRequested)
{
- if (PrimaryConnInfo == NULL && recoveryRestoreCommand == NULL)
+ if (conf->PrimaryConnInfo == NULL && conf->recoveryRestoreCommand == NULL)
ereport(WARNING,
(errmsg("recovery command file \"%s\" specified neither primary_conninfo nor restore_command",
RECOVERY_COMMAND_FILE),
@@ -5487,11 +5592,14 @@ readRecoveryCommandFile(void)
}
else
{
- if (recoveryRestoreCommand == NULL)
- ereport(FATAL,
+ if (conf->recoveryRestoreCommand == NULL)
+ {
+ ereport(fatalReportLevel,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("recovery command file \"%s\" must specify restore_command when standby mode is not enabled",
RECOVERY_COMMAND_FILE)));
+ configHasErrors = true;
+ }
}
/*
@@ -5499,51 +5607,225 @@ readRecoveryCommandFile(void)
* behaviour in 9.5; prior to this we simply ignored a request to pause if
* hot_standby = off, which was surprising behaviour.
*/
- if (recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE &&
+ if (conf->recoveryTargetAction == RECOVERY_TARGET_ACTION_PAUSE &&
recoveryTargetActionSet &&
!EnableHotStandby)
- recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
+ conf->recoveryTargetAction = RECOVERY_TARGET_ACTION_SHUTDOWN;
/*
* We don't support standby_mode in standalone backends; that requires
* other processes such as the WAL receiver to be alive.
*/
- if (StandbyModeRequested && !IsUnderPostmaster)
+ if (conf->StandbyModeRequested && !IsUnderPostmaster)
ereport(FATAL,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("standby mode is not supported by single-user servers")));
- /* Enable fetching from archive recovery area */
- ArchiveRecoveryRequested = true;
+ return !configHasErrors;
+}
+
+/*
+ * get restore_command for xlogarchive.c
+ */
+char *
+GetRecoveryRestoreCommand(void)
+{
+ return recoveryConfig->recoveryRestoreCommand;
+}
+
+#define RecoveryConfigStringIsChanged(old, new) !(\
+ ((old) == NULL && (new) == NULL) || \
+ ((new) != NULL && (old) != NULL && strcmp((new),(old)) == 0) )
+#define RecoveryConfigParamRequiredRestart(param) \
+ ereport(LOG, \
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM), \
+ errmsg("parameter \"%s\" cannot be changed without restarting the server", \
+ (param))));
+
+/*
+ * Set the parameters for archive recovery and XLOG streaming.
+ */
+void
+ProcessRecoveryCommandFile(bool isReload)
+{
+ recoveryCommandData *newConf = DefaultRecoveryCommandFile();
+ bool isConfigValid;
+
+ isConfigValid = readRecoveryCommandFile(isReload, newConf);
+
+ if (!isConfigValid)
+ {
+ ereport(isReload ? LOG : FATAL,
+ (errcode(ERRCODE_CONFIG_FILE_ERROR),
+ errmsg("configuration file \"%s\" contains errors",
+ RECOVERY_COMMAND_FILE)));
+ FreeRecoveryCommandFile(newConf);
+ return;
+ }
+
+ if (!isReload)
+ {
+ Assert(recoveryConfig == NULL);
+ recoveryConfig = newConf;
+
+ if (!newConf->confExists)
+ {
+ /* recovery command file not present, so no archive recovery */
+ return;
+ }
+
+ /* Enable fetching from archive recovery area */
+ ArchiveRecoveryRequested = true;
+
+ /*
+ * If user specified recovery_target_timeline, validate it or compute
+ * the "latest" value. We can't do this until after we've gotten the
+ * restore command and set InArchiveRecovery, because we need to fetch
+ * timeline history files from the archive.
+ */
+ if (newConf->recoveryTargetTimelineGiven)
+ {
+ if (newConf->recoveryTargetTimeline)
+ {
+ /* Timeline 1 does not have a history file, all else should */
+ if (newConf->recoveryTargetTimeline != 1 &&
+ !existsTimeLineHistory(newConf->recoveryTargetTimeline))
+ ereport(FATAL,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("recovery target timeline %u does not exist",
+ newConf->recoveryTargetTimeline)));
+ recoveryTargetTLI = newConf->recoveryTargetTimeline;
+ recoveryTargetIsLatest = false;
+ }
+ else
+ {
+ /* We start the "latest" search from pg_control's timeline */
+ recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
+ recoveryTargetIsLatest = true;
+ }
+ }
+ return;
+ }
/*
- * If user specified recovery_target_timeline, validate it or compute the
- * "latest" value. We can't do this until after we've gotten the restore
- * command and set InArchiveRecovery, because we need to fetch timeline
- * history files from the archive.
+ * readRecoveryCommandFile already report about missed recovery file
*/
- if (rtliGiven)
+ if (newConf->confExists)
{
- if (rtli)
+ MemoryContext oldcontext;
+ bool requestWalRcvRestart = false;
+
+ Assert(AmStartupProcess());
+
+ /* we can need allocate new TopMemoryContext string */
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ if (RecoveryConfigStringIsChanged(newConf->recoveryRestoreCommand,
+ recoveryConfig->recoveryRestoreCommand))
{
- /* Timeline 1 does not have a history file, all else should */
- if (rtli != 1 && !existsTimeLineHistory(rtli))
- ereport(FATAL,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("recovery target timeline %u does not exist",
- rtli)));
- recoveryTargetTLI = rtli;
- recoveryTargetIsLatest = false;
+ if (recoveryConfig->recoveryRestoreCommand != NULL)
+ pfree(recoveryConfig->recoveryRestoreCommand);
+ recoveryConfig->recoveryRestoreCommand = newConf->recoveryRestoreCommand ? pstrdup(newConf->recoveryRestoreCommand) : NULL;
+ ereport(LOG,
+ (errmsg("parameter \"%s\" changed to \"%s\"",
+ "restore_command", recoveryConfig->recoveryRestoreCommand)));
}
- else
+ if (RecoveryConfigStringIsChanged(newConf->recoveryEndCommand,
+ recoveryConfig->recoveryEndCommand))
+ RecoveryConfigParamRequiredRestart("recovery_end_command");
+ if (RecoveryConfigStringIsChanged(newConf->archiveCleanupCommand,
+ recoveryConfig->archiveCleanupCommand))
+ RecoveryConfigParamRequiredRestart("archive_cleanup_command");
+ if (newConf->recoveryTargetAction != recoveryConfig->recoveryTargetAction)
+ RecoveryConfigParamRequiredRestart("recovery_target_action");
+ if (newConf->recoveryTargetTimeline != recoveryConfig->recoveryTargetTimeline ||
+ newConf->recoveryTargetTimelineGiven != recoveryConfig->recoveryTargetTimelineGiven)
+ RecoveryConfigParamRequiredRestart("recovery_target_timeline");
+ switch (recoveryConfig->recoveryTarget)
+ {
+ case RECOVERY_TARGET_UNSET:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_UNSET)
+ ereport(LOG,
+ (errcode(ERRCODE_CANT_CHANGE_RUNTIME_PARAM),
+ errmsg("cannot set recovery target without restarting the server")));
+ break;
+ case RECOVERY_TARGET_XID:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_XID ||
+ recoveryConfig->recoveryTargetXid != newConf->recoveryTargetXid)
+ RecoveryConfigParamRequiredRestart("recovery_target_xid");
+ break;
+ case RECOVERY_TARGET_TIME:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_TIME ||
+ recoveryConfig->recoveryTargetTime != newConf->recoveryTargetTime)
+ RecoveryConfigParamRequiredRestart("recovery_target_time");
+ break;
+ case RECOVERY_TARGET_NAME:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_NAME ||
+ recoveryConfig->recoveryTargetName != newConf->recoveryTargetName)
+ RecoveryConfigParamRequiredRestart("recovery_target_name");
+ break;
+ case RECOVERY_TARGET_LSN:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_LSN ||
+ recoveryConfig->recoveryTargetLSN != newConf->recoveryTargetLSN)
+ RecoveryConfigParamRequiredRestart("recovery_target_lsn");
+ break;
+ case RECOVERY_TARGET_IMMEDIATE:
+ if (newConf->recoveryTarget != RECOVERY_TARGET_IMMEDIATE)
+ RecoveryConfigParamRequiredRestart("recovery_target");
+ break;
+ }
+ if (recoveryConfig->recoveryTargetInclusive != newConf->recoveryTargetInclusive)
+ RecoveryConfigParamRequiredRestart("recovery_target_inclusive");
+ if (recoveryConfig->StandbyModeRequested != newConf->StandbyModeRequested)
+ RecoveryConfigParamRequiredRestart("standby_mode");
+ if (RecoveryConfigStringIsChanged(newConf->PrimaryConnInfo,
+ recoveryConfig->PrimaryConnInfo))
{
- /* We start the "latest" search from pg_control's timeline */
- recoveryTargetTLI = findNewestTimeLine(recoveryTargetTLI);
- recoveryTargetIsLatest = true;
+ if (recoveryConfig->PrimaryConnInfo != NULL)
+ pfree(recoveryConfig->PrimaryConnInfo);
+ recoveryConfig->PrimaryConnInfo = newConf->PrimaryConnInfo ? pstrdup(newConf->PrimaryConnInfo) : NULL;
+ /* conninfo may include plaintext password, so not log new value */
+ ereport(LOG,
+ (errmsg("parameter \"%s\" changed", "primary_conninfo")));
+ requestWalRcvRestart = true;
+ }
+ if (RecoveryConfigStringIsChanged(newConf->PrimarySlotName,
+ recoveryConfig->PrimarySlotName))
+ {
+ if (recoveryConfig->PrimarySlotName != NULL)
+ pfree(recoveryConfig->PrimarySlotName);
+ recoveryConfig->PrimarySlotName = newConf->PrimarySlotName ? pstrdup(newConf->PrimarySlotName) : NULL;
+ ereport(LOG,
+ (errmsg("parameter \"%s\" changed to \"%s\"",
+ "primary_slot_name", recoveryConfig->PrimarySlotName)));
+ requestWalRcvRestart = true;
+ }
+ if (RecoveryConfigStringIsChanged(newConf->TriggerFile,
+ recoveryConfig->TriggerFile))
+ {
+ if (recoveryConfig->TriggerFile != NULL)
+ pfree(recoveryConfig->TriggerFile);
+ recoveryConfig->TriggerFile = newConf->TriggerFile ? pstrdup(newConf->TriggerFile) : NULL;
+ ereport(LOG,
+ (errmsg("parameter \"%s\" changed to \"%s\"",
+ "trigger_file", recoveryConfig->TriggerFile)));
+ }
+ if (recoveryConfig->recoveryMinApplyDelay != newConf->recoveryMinApplyDelay)
+ RecoveryConfigParamRequiredRestart("recovery_min_apply_delay");
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (requestWalRcvRestart)
+ {
+ /*
+ * walreceiver settings was changed, shutdown here and will be
+ * started a bit later in WaitForWALToBecomeAvailable
+ */
+ ShutdownWalRcv();
}
}
- FreeConfigVariables(head);
+ FreeRecoveryCommandFile(newConf);
}
/*
@@ -5707,7 +5989,8 @@ recoveryStopsBefore(XLogReaderState *record)
TransactionId recordXid;
/* Check if we should stop as soon as reaching consistency */
- if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE && reachedConsistency)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_IMMEDIATE &&
+ reachedConsistency)
{
ereport(LOG,
(errmsg("recovery stopping after reaching consistency")));
@@ -5721,9 +6004,9 @@ recoveryStopsBefore(XLogReaderState *record)
}
/* Check if target LSN has been reached */
- if (recoveryTarget == RECOVERY_TARGET_LSN &&
- !recoveryTargetInclusive &&
- record->ReadRecPtr >= recoveryTargetLSN)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_LSN &&
+ !recoveryConfig->recoveryTargetInclusive &&
+ record->ReadRecPtr >= recoveryConfig->recoveryTargetLSN)
{
recoveryStopAfter = false;
recoveryStopXid = InvalidTransactionId;
@@ -5778,7 +6061,8 @@ recoveryStopsBefore(XLogReaderState *record)
else
return false;
- if (recoveryTarget == RECOVERY_TARGET_XID && !recoveryTargetInclusive)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_XID &&
+ !recoveryConfig->recoveryTargetInclusive)
{
/*
* There can be only one transaction end record with this exact
@@ -5789,10 +6073,10 @@ recoveryStopsBefore(XLogReaderState *record)
* they complete. A higher numbered xid will complete before you about
* 50% of the time...
*/
- stopsHere = (recordXid == recoveryTargetXid);
+ stopsHere = (recordXid == recoveryConfig->recoveryTargetXid);
}
- if (recoveryTarget == RECOVERY_TARGET_TIME &&
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_TIME &&
getRecordTimestamp(record, &recordXtime))
{
/*
@@ -5800,10 +6084,10 @@ recoveryStopsBefore(XLogReaderState *record)
* we stop after the last one, if we are inclusive, or stop at the
* first one if we are exclusive
*/
- if (recoveryTargetInclusive)
- stopsHere = (recordXtime > recoveryTargetTime);
+ if (recoveryConfig->recoveryTargetInclusive)
+ stopsHere = (recordXtime > recoveryConfig->recoveryTargetTime);
else
- stopsHere = (recordXtime >= recoveryTargetTime);
+ stopsHere = (recordXtime >= recoveryConfig->recoveryTargetTime);
}
if (stopsHere)
@@ -5854,14 +6138,14 @@ recoveryStopsAfter(XLogReaderState *record)
* There can be many restore points that share the same name; we stop at
* the first one.
*/
- if (recoveryTarget == RECOVERY_TARGET_NAME &&
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_NAME &&
rmid == RM_XLOG_ID && info == XLOG_RESTORE_POINT)
{
xl_restore_point *recordRestorePointData;
recordRestorePointData = (xl_restore_point *) XLogRecGetData(record);
- if (strcmp(recordRestorePointData->rp_name, recoveryTargetName) == 0)
+ if (strcmp(recordRestorePointData->rp_name, recoveryConfig->recoveryTargetName) == 0)
{
recoveryStopAfter = true;
recoveryStopXid = InvalidTransactionId;
@@ -5878,9 +6162,9 @@ recoveryStopsAfter(XLogReaderState *record)
}
/* Check if the target LSN has been reached */
- if (recoveryTarget == RECOVERY_TARGET_LSN &&
- recoveryTargetInclusive &&
- record->ReadRecPtr >= recoveryTargetLSN)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_LSN &&
+ recoveryConfig->recoveryTargetInclusive &&
+ record->ReadRecPtr >= recoveryConfig->recoveryTargetLSN)
{
recoveryStopAfter = true;
recoveryStopXid = InvalidTransactionId;
@@ -5943,8 +6227,9 @@ recoveryStopsAfter(XLogReaderState *record)
* they complete. A higher numbered xid will complete before you about
* 50% of the time...
*/
- if (recoveryTarget == RECOVERY_TARGET_XID && recoveryTargetInclusive &&
- recordXid == recoveryTargetXid)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_XID &&
+ recoveryConfig->recoveryTargetInclusive &&
+ recordXid == recoveryConfig->recoveryTargetXid)
{
recoveryStopAfter = true;
recoveryStopXid = recordXid;
@@ -5973,7 +6258,8 @@ recoveryStopsAfter(XLogReaderState *record)
}
/* Check if we should stop as soon as reaching consistency */
- if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE && reachedConsistency)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_IMMEDIATE &&
+ reachedConsistency)
{
ereport(LOG,
(errmsg("recovery stopping after reaching consistency")));
@@ -6056,7 +6342,7 @@ recoveryApplyDelay(XLogReaderState *record)
int microsecs;
/* nothing to do if no delay configured */
- if (recovery_min_apply_delay <= 0)
+ if (recoveryConfig->recoveryMinApplyDelay <= 0)
return false;
/* no delay is applied on a database not yet consistent */
@@ -6084,7 +6370,7 @@ recoveryApplyDelay(XLogReaderState *record)
return false;
recoveryDelayUntilTime =
- TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);
+ TimestampTzPlusMilliseconds(xtime, recoveryConfig->recoveryMinApplyDelay);
/*
* Exit without arming the latch if it's already past time to apply this
@@ -6377,39 +6663,39 @@ StartupXLOG(void)
* Check for recovery control file, and if so set up state for offline
* recovery
*/
- readRecoveryCommandFile();
+ ProcessRecoveryCommandFile(false);
/*
* Save archive_cleanup_command in shared memory so that other processes
* can see it.
*/
strlcpy(XLogCtl->archiveCleanupCommand,
- archiveCleanupCommand ? archiveCleanupCommand : "",
+ recoveryConfig->archiveCleanupCommand ? recoveryConfig->archiveCleanupCommand : "",
sizeof(XLogCtl->archiveCleanupCommand));
if (ArchiveRecoveryRequested)
{
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
ereport(LOG,
(errmsg("entering standby mode")));
- else if (recoveryTarget == RECOVERY_TARGET_XID)
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_XID)
ereport(LOG,
(errmsg("starting point-in-time recovery to XID %u",
- recoveryTargetXid)));
- else if (recoveryTarget == RECOVERY_TARGET_TIME)
+ recoveryConfig->recoveryTargetXid)));
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_TIME)
ereport(LOG,
(errmsg("starting point-in-time recovery to %s",
- timestamptz_to_str(recoveryTargetTime))));
- else if (recoveryTarget == RECOVERY_TARGET_NAME)
+ timestamptz_to_str(recoveryConfig->recoveryTargetTime))));
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_NAME)
ereport(LOG,
(errmsg("starting point-in-time recovery to \"%s\"",
- recoveryTargetName)));
- else if (recoveryTarget == RECOVERY_TARGET_LSN)
+ recoveryConfig->recoveryTargetName)));
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_LSN)
ereport(LOG,
(errmsg("starting point-in-time recovery to WAL location (LSN) \"%X/%X\"",
- (uint32) (recoveryTargetLSN >> 32),
- (uint32) recoveryTargetLSN)));
- else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
+ (uint32) (recoveryConfig->recoveryTargetLSN >> 32),
+ (uint32) recoveryConfig->recoveryTargetLSN)));
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
ereport(LOG,
(errmsg("starting point-in-time recovery to earliest consistent point")));
else
@@ -6421,7 +6707,7 @@ StartupXLOG(void)
* Take ownership of the wakeup latch if we're going to sleep during
* recovery.
*/
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
OwnLatch(&XLogCtl->recoveryWakeupLatch);
/* Set up XLOG reader facility */
@@ -6452,7 +6738,7 @@ StartupXLOG(void)
* archive recovery directly.
*/
InArchiveRecovery = true;
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
StandbyMode = true;
/*
@@ -6579,7 +6865,7 @@ StartupXLOG(void)
ControlFile->state == DB_SHUTDOWNED))
{
InArchiveRecovery = true;
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
StandbyMode = true;
}
@@ -7318,7 +7604,7 @@ StartupXLOG(void)
* this, Resource Managers may choose to do permanent
* corrective actions at end of recovery.
*/
- switch (recoveryTargetAction)
+ switch (recoveryConfig->recoveryTargetAction)
{
case RECOVERY_TARGET_ACTION_SHUTDOWN:
@@ -7387,7 +7673,7 @@ StartupXLOG(void)
* We don't need the latch anymore. It's not strictly necessary to disown
* it, but let's do it for the sake of tidiness.
*/
- if (StandbyModeRequested)
+ if (recoveryConfig->StandbyModeRequested)
DisownLatch(&XLogCtl->recoveryWakeupLatch);
/*
@@ -7478,27 +7764,27 @@ StartupXLOG(void)
* Create a comment for the history file to explain why and where
* timeline changed.
*/
- if (recoveryTarget == RECOVERY_TARGET_XID)
+ if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_XID)
snprintf(reason, sizeof(reason),
"%s transaction %u",
recoveryStopAfter ? "after" : "before",
recoveryStopXid);
- else if (recoveryTarget == RECOVERY_TARGET_TIME)
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_TIME)
snprintf(reason, sizeof(reason),
"%s %s\n",
recoveryStopAfter ? "after" : "before",
timestamptz_to_str(recoveryStopTime));
- else if (recoveryTarget == RECOVERY_TARGET_LSN)
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_LSN)
snprintf(reason, sizeof(reason),
"%s LSN %X/%X\n",
recoveryStopAfter ? "after" : "before",
(uint32) (recoveryStopLSN >> 32),
(uint32) recoveryStopLSN);
- else if (recoveryTarget == RECOVERY_TARGET_NAME)
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_NAME)
snprintf(reason, sizeof(reason),
"at restore point \"%s\"",
recoveryStopName);
- else if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
+ else if (recoveryConfig->recoveryTarget == RECOVERY_TARGET_IMMEDIATE)
snprintf(reason, sizeof(reason), "reached consistency");
else
snprintf(reason, sizeof(reason), "no recovery target specified");
@@ -7642,8 +7928,8 @@ StartupXLOG(void)
/*
* And finally, execute the recovery_end_command, if any.
*/
- if (recoveryEndCommand)
- ExecuteRecoveryCommand(recoveryEndCommand,
+ if (recoveryConfig->recoveryEndCommand)
+ ExecuteRecoveryCommand(recoveryConfig->recoveryEndCommand,
"recovery_end_command",
true);
}
@@ -11816,7 +12102,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
* that when we later jump backwards to start redo at
* RedoStartLSN, we will have the logs streamed already.
*/
- if (PrimaryConnInfo)
+ if (recoveryConfig->PrimaryConnInfo)
{
XLogRecPtr ptr;
TimeLineID tli;
@@ -11837,8 +12123,9 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
tli, curFileTLI);
}
curFileTLI = tli;
- RequestXLogStreaming(tli, ptr, PrimaryConnInfo,
- PrimarySlotName);
+ RequestXLogStreaming(tli, ptr,
+ recoveryConfig->PrimaryConnInfo,
+ recoveryConfig->PrimarySlotName);
receivedUpto = 0;
}
@@ -12179,14 +12466,14 @@ CheckForStandbyTrigger(void)
return true;
}
- if (TriggerFile == NULL)
+ if (recoveryConfig->TriggerFile == NULL)
return false;
- if (stat(TriggerFile, &stat_buf) == 0)
+ if (stat(recoveryConfig->TriggerFile, &stat_buf) == 0)
{
ereport(LOG,
- (errmsg("trigger file found: %s", TriggerFile)));
- unlink(TriggerFile);
+ (errmsg("trigger file found: %s", recoveryConfig->TriggerFile)));
+ unlink(recoveryConfig->TriggerFile);
triggered = true;
fast_promote = true;
return true;
@@ -12195,7 +12482,7 @@ CheckForStandbyTrigger(void)
ereport(ERROR,
(errcode_for_file_access(),
errmsg("could not stat trigger file \"%s\": %m",
- TriggerFile)));
+ recoveryConfig->TriggerFile)));
return false;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 5c6de49..6773a06 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -64,6 +64,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
TimeLineID restartTli;
+ char *recoveryRestoreCommand = GetRecoveryRestoreCommand();
/* In standby mode, restore_command might not be supplied */
if (recoveryRestoreCommand == NULL)
diff --git a/src/backend/utils/misc/guc-file.l b/src/backend/utils/misc/guc-file.l
index afe4fe6..e9b81f9 100644
--- a/src/backend/utils/misc/guc-file.l
+++ b/src/backend/utils/misc/guc-file.l
@@ -155,6 +155,14 @@ ProcessConfigFile(GucContext context)
*/
(void) ProcessConfigFileInternal(context, true, elevel);
+ /*
+ * Startup process need check recovery.conf changes
+ */
+ if (context == PGC_SIGHUP && AmStartupProcess())
+ {
+ ProcessRecoveryCommandFile(true);
+ }
+
/* Clean up */
MemoryContextSwitchTo(caller_cxt);
MemoryContextDelete(config_cxt);
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index 421ba6d..fce9ec5 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -288,6 +288,8 @@ extern void XLogRequestWalReceiverReply(void);
extern void assign_max_wal_size(int newval, void *extra);
extern void assign_checkpoint_completion_target(double newval, void *extra);
+extern void ProcessRecoveryCommandFile(bool isReload);
+
/*
* Routines to start, stop, and get status of a base backup.
*/
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 7c76683..ef9484d 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -313,7 +313,7 @@ extern void GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli);
extern bool ArchiveRecoveryRequested;
extern bool InArchiveRecovery;
extern bool StandbyMode;
-extern char *recoveryRestoreCommand;
+extern char *GetRecoveryRestoreCommand(void);
/*
* Prototypes for functions in xlogarchive.c
diff --git a/src/test/recovery/t/001_stream_rep.pl b/src/test/recovery/t/001_stream_rep.pl
index a29a6c7..b925fb1 100644
--- a/src/test/recovery/t/001_stream_rep.pl
+++ b/src/test/recovery/t/001_stream_rep.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 28;
+use Test::More tests => 29;
# Initialize master node
my $node_master = get_new_node('master');
@@ -144,7 +144,7 @@ $node_standby_2->append_conf('recovery.conf',
"primary_slot_name = $slotname_2");
$node_standby_2->append_conf('postgresql.conf',
"wal_receiver_status_interval = 1");
-$node_standby_2->restart;
+$node_standby_2->reload; # can switch slot without restart
# Fetch xmin columns from slot's pg_replication_slots row, after waiting for
# given boolean condition to be true to ensure we've reached a quiescent state
@@ -303,3 +303,24 @@ $node_standby_2->start;
get_slot_xmins($node_standby_1, $slotname_2, "xmin IS NULL");
is($xmin, '',
'xmin of cascaded slot reset after startup with hs feedback reset');
+
+note "check continue streaming with missed recovery conf on reload";
+unlink($node_standby_2->data_dir . '/recovery.conf');
+$node_standby_2->reload;
+replay_check();
+
+note "check change primary_conninfo without restart";
+$node_standby_2->enable_streaming($node_master);
+$node_standby_2->reload;
+
+# be sure do not streaming from cascade
+$node_standby_1->stop;
+
+my $newval = $node_master->safe_psql('postgres',
+'INSERT INTO replayed(val) SELECT coalesce(max(val),0) + 1 AS newval FROM replayed RETURNING val'
+);
+$node_master->wait_for_catchup($node_standby_2, 'replay',
+ $node_master->lsn('insert'));
+my $is_replayed = $node_standby_2->safe_psql('postgres',
+ qq[SELECT 1 FROM replayed WHERE val = $newval]);
+is($is_replayed, qq(1), "standby_2 didn't replay master value $newval");
diff --git a/src/test/recovery/t/002_archiving.pl b/src/test/recovery/t/002_archiving.pl
index e1bd3c9..ab4524d 100644
--- a/src/test/recovery/t/002_archiving.pl
+++ b/src/test/recovery/t/002_archiving.pl
@@ -3,7 +3,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 1;
+use Test::More tests => 3;
use File::Copy;
# Initialize master node, doing archives
@@ -49,3 +49,34 @@ $node_standby->poll_query_until('postgres', $caughtup_query)
my $result =
$node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
is($result, qq(1000), 'check content from archives');
+
+note "test reload";
+$node_standby->stop;
+unlink($node_standby->data_dir . '/recovery.conf');
+$node_standby->append_conf('recovery.conf', 'standby_mode=on'); #no archive_recovery
+
+# Force archiving of WAL file to make it present on master
+$node_master->safe_psql('postgres', "SELECT pg_switch_wal()");
+
+$node_standby->start;
+
+# still no new data
+$result =
+ $node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result, qq(1000), 'check content from archives before reload');
+
+$node_standby->enable_restoring($node_master);
+$node_standby->reload;
+
+$current_lsn =
+ $node_master->safe_psql('postgres', "SELECT pg_current_wal_lsn();");
+# Wait until necessary replay has been done on standby
+$caughtup_query =
+ "SELECT '$current_lsn'::pg_lsn <= pg_last_wal_replay_lsn()";
+$node_standby->poll_query_until('postgres', $caughtup_query)
+ or die "Timed out while waiting for standby to catch up";
+
+# new data after reload are here
+$result =
+ $node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
+is($result, qq(2000), 'check content from archives before reload');
diff --git a/src/test/recovery/t/015_trigger_file.pl b/src/test/recovery/t/015_trigger_file.pl
new file mode 100644
index 0000000..0ef1b57
--- /dev/null
+++ b/src/test/recovery/t/015_trigger_file.pl
@@ -0,0 +1,61 @@
+# test for promote with trigger_file
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+use File::Copy;
+
+my $triggers_dir = TestLib::tempdir('tmp_trigger_file');
+
+# Initialize master node
+my $node_master = get_new_node('master');
+$node_master->init(
+ has_archiving => 1,
+ allows_streaming => 1);
+my $backup_name = 'my_backup';
+
+# Start it
+$node_master->start;
+
+# Take backup for standby
+$node_master->backup($backup_name);
+
+# Initialize standby nodes from backup
+# standby 1 check startup parameter
+my $node_standby_1 = get_new_node('standby_1');
+$node_standby_1->init_from_backup($node_master, $backup_name, has_restoring => 1);
+my $standby_1_trigger_file = $triggers_dir . '/trigger_1';
+$node_standby_1->append_conf('recovery.conf',
+ "trigger_file = '$standby_1_trigger_file'");
+$node_standby_1->start;
+
+# standby 2 will be reload
+my $node_standby_2 = get_new_node('standby_2');
+$node_standby_2->init_from_backup($node_master, $backup_name, has_restoring => 1);
+my $standby_2_trigger_file = $triggers_dir . '/trigger_2';
+$node_standby_2->start;
+
+sub check_in_recovery
+{
+ my ($node) = @_;
+ my $result = $node->safe_psql('postgres', "SELECT pg_is_in_recovery()::int");
+ is($result, '1', $node->name . ' in recovery');
+}
+
+check_in_recovery($node_standby_1);
+check_in_recovery($node_standby_2);
+
+TestLib::append_to_file($standby_1_trigger_file, '');
+TestLib::append_to_file($standby_2_trigger_file, '');
+
+$node_standby_1->poll_query_until('postgres', 'SELECT NOT pg_is_in_recovery()')
+ or die "Timed out while waiting for standby 1 to promote";
+
+check_in_recovery($node_standby_2); # be sure still in recovery
+$node_standby_2->append_conf('recovery.conf',
+ "trigger_file = '$standby_2_trigger_file'");
+$node_standby_2->reload;
+
+$node_standby_2->poll_query_until('postgres', 'SELECT NOT pg_is_in_recovery()')
+ or die "Timed out while waiting for standby 2 to promote";;