recovery modules
I've attached a patch set that adds the restore_library,
archive_cleanup_library, and recovery_end_library parameters to allow
archive recovery via loadable modules. This is a follow-up to the
archive_library parameter added in v15 [0]/messages/by-id/668D2428-F73B-475E-87AE-F89D67942270@amazon.com [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=5ef1eef.
The motivation behind this change is similar to that of archive_library
(e.g., robustness, performance). The recovery functions are provided via a
similar interface to archive modules (i.e., an initialization function that
returns the function pointers). Also, I've extended basic_archive to work
as a restore_library, which makes it easy to demonstrate both archiving and
recovery via a loadable module in a TAP test.
A few miscellaneous design notes:
* Unlike archive modules, recovery libraries cannot be changed at runtime.
There isn't a safe way to unload a library, and archive libraries work
around this restriction by restarting the archiver process. Since recovery
libraries are loaded via the startup and checkpointer processes (which
cannot be trivially restarted like the archiver), the same workaround is
not feasible.
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.
* I've combined the documentation to create one "Archive and Recovery
Modules" chapter. They are similar enough that it felt silly to write a
separate chapter for recovery modules. However, I've still split them up
within the chapter, and they have separate initialization functions. This
retains backward compatibility with v15 archive modules, keeps them
logically separate, and hopefully hints at the functional differences.
Even so, if you want to create one library for both archive and recovery,
there is nothing stopping you.
* Unlike archive modules, I didn't add any sort of "check" or "shutdown"
callbacks. The recovery_end_library parameter makes a "shutdown" callback
largely redundant, and I couldn't think of any use-case for a "check"
callback. However, new callbacks could be added in the future if needed.
* Unlike archive modules, restore_library and recovery_end_library may be
loaded in single-user mode. I believe this works out-of-the-box, but it's
an extra thing to be cognizant of.
* If both the library and command parameter for a recovery action is
specified, the server should fail to startup, but if a misconfiguration is
detected after SIGHUP, we emit a WARNING and continue using the library. I
originally thought about emitting an ERROR like the archiver does in this
case, but failing recovery and stopping the server felt a bit too harsh.
I'm curious what folks think about this.
* Іt could be nice to rewrite pg_archivecleanup for use as an
archive_cleanup_library, but I don't think that needs to be a part of this
patch set.
[0]: /messages/by-id/668D2428-F73B-475E-87AE-F89D67942270@amazon.com
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=5ef1eef
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v1-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From b2e826baef398998ba93b27c5d68e89d439b4962 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v1 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 194 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 44 ++++-
src/backend/access/transam/xlogarchive.c | 158 +----------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 240 insertions(+), 165 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 65c77531be..a0870217b8 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..3ddcabd969
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+ if (cmd == NULL)
+ elog(ERROR, "could not build restore command \"%s\"", cmd);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char xlogRecoveryCmd[MAXPGPATH];
+ char *dp;
+ char *endp;
+ const char *sp;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ dp = xlogRecoveryCmd;
+ endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ *endp = '\0';
+
+ for (sp = command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'r':
+ /* %r: filename of last restartpoint */
+ sp++;
+ strlcpy(dp, lastRestartPointFileName, endp - dp);
+ dp += strlen(dp);
+ break;
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ }
+ }
+ else
+ {
+ if (dp < endp)
+ *dp++ = *sp;
+ }
+ }
+ *dp = '\0';
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 91473b00d9..32225be4a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,10 +4887,24 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7321,24 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index e2b7176f2f..50b0d1105d 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -56,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -149,18 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
- if (xlogRestoreCmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"",
- recoveryRestoreCommand);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -169,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -233,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -277,110 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char xlogRecoveryCmd[MAXPGPATH];
- char lastRestartPointFname[MAXPGPATH];
- char *dp;
- char *endp;
- const char *sp;
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
- *endp = '\0';
-
- for (sp = command; *sp; sp++)
- {
- if (*sp == '%')
- {
- switch (sp[1])
- {
- case 'r':
- /* %r: filename of last restartpoint */
- sp++;
- strlcpy(dp, lastRestartPointFname, endp - dp);
- dp += strlen(dp);
- break;
- case '%':
- /* convert %% to a single % */
- sp++;
- if (dp < endp)
- *dp++ = *sp;
- break;
- default:
- /* otherwise treat the % as not special */
- if (dp < endp)
- *dp++ = *sp;
- break;
- }
- }
- else
- {
- if (dp < endp)
- *dp++ = *sp;
- }
- }
- *dp = '\0';
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index f47b219538..69d002cdeb 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v1-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 05c6289afa39256248defa32e3fd4cc78ecf76d5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v1 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 108 +++++++++++----------
1 file changed, 58 insertions(+), 50 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 3ddcabd969..073e709e06 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -22,17 +22,19 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static char *BuildCleanupCommand(const char *command,
+ const char *lastRestartPointFileName);
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
@@ -40,19 +42,6 @@ shell_restore(const char *file, const char *path,
if (cmd == NULL)
elog(ERROR, "could not build restore command \"%s\"", cmd);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -77,60 +66,52 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(archiveCleanupCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(recoveryEndCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Build a recovery_end_command or archive_cleanup_command. The return value
+ * is palloc'd.
*/
-static void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+static char *
+BuildCleanupCommand(const char *command, const char *lastRestartPointFileName)
{
- char xlogRecoveryCmd[MAXPGPATH];
+ char *ret = (char *) palloc(MAXPGPATH);
char *dp;
char *endp;
const char *sp;
- int rc;
-
- Assert(command && commandName);
/*
* construct the command to be executed
*/
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ dp = ret;
+ endp = ret + MAXPGPATH - 1;
*endp = '\0';
for (sp = command; *sp; sp++)
@@ -166,6 +147,28 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
}
*dp = '\0';
+ return ret;
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
+ *
+ * Returns whether the command succeeded.
+ */
+static bool
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
+{
+ int rc;
+
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -174,16 +177,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -191,4 +197,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v1-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From f9066c1497ab22312cd28dc3d204ca3b5220350c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v1 3/3] Allow recovery via loadable modules.
This adds the restore_library, archive_cleanup_library, and
recovery_end_library parameters to allow archive recovery via a
loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 69 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 42 +++++
doc/src/sgml/archive-modules.sgml | 176 ++++++++++++++++--
doc/src/sgml/backup.sgml | 40 +++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 99 +++++++++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 131 ++++++++++++-
src/backend/access/transam/xlogrecovery.c | 20 +-
src/backend/postmaster/checkpointer.c | 23 +++
src/backend/postmaster/startup.c | 22 ++-
src/backend/utils/misc/guc_tables.c | 30 +++
src/backend/utils/misc/postgresql.conf.sample | 3 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 43 ++++-
src/include/access/xlogrecovery.h | 3 +
20 files changed, 724 insertions(+), 74 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 9f221816bb..2a702455ca 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,47 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+ fsync_fname(path, false);
+ fsync_fname(archive_directory, true);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b25dce99a3..5b5a6d79e7 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..86ff0dc7f5
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..023f2e047a 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,43 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/>,
+ <xref linkend="guc-archive-cleanup-library"/>, or
+ <xref linkend="guc-recovery-end-library"/> is configured, PostgreSQL will use
+ the module for the corresponding recovery action. It is ultimately up to the
+ module to decide how to accomplish each task, but some recommendations are
+ listed at <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for any combination of the
+ configuration parameters mentioned in the preceding paragraph, or it can be
+ used for just one.
</para>
<para>
@@ -37,7 +46,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +73,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -133,4 +148,135 @@ typedef void (*ArchiveShutdownCB) (void);
</para>
</sect2>
</sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/>,
+ <xref linkend="guc-archive-cleanup-library"/>, or
+ <xref linkend="guc-recovery-end-library"/> as the library base name. The
+ normal library search path is used to locate the library. To provide the
+ required recovery module callbacks and to indicate that the library is
+ actually a recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ If the recovery module is loaded via <varname>restore_library</varname>, the
+ <function>restore_cb</function> callback is required. If the recovery
+ module is loaded via <varname>archive_cleanup_library</varname>, the
+ <function>archive_cleanup_cb</function> callback is required. If the
+ recovery module is loaded via <varname>recovery_end_library</varname>, the
+ <function>recovery_end_library</function> callback is required. The same
+ recovery module may be used for more than one of the aforementioned
+ parameters if desired. Unused callback functions (e.g., if
+ <function>restore_cb</function> is defined but the library is only loaded
+ via <varname>recovery_end_library</varname>) are ignored.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> and
+ <varname>recovery_end_library</varname> are only loaded in the startup
+ process and in single-user mode, while
+ <varname>archive_cleanup_library</varname> is only loaded in the
+ checkpointer process.
+ </para>
+ </note>
+
+ <note>
+ <para>
+ A recovery module's <function>_PG_recovery_module_init</function> might be
+ called multiple times in the same process. If a module uses this function
+ for anything beyond returning its callback functions, it must be able to cope
+ with multiple invocations.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..8cf3f35649 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 0b650f17a8..ac7cd9b967 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2>
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3071c8eace..5cf9d0a42a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,36 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for retrieving an archived segment of the WAL file
+ series. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname> is used. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ Otherwise, the specified shared library is used for restoring. For
+ more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3912,37 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>archive_cleanup_library</varname> is set to an empty string.
+ If both <varname>archive_cleanup_command</varname> and
+ <varname>archive_cleanup_library</varname> are set, an error will be
+ raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-archive-cleanup-library" xreflabel="archive_cleanup_library">
+ <term><varname>archive_cleanup_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>archive_cleanup_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ This optional parameter specifies a library that will be executed at
+ every restartpoint. The purpose of this parameter is to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server. If this parameter is set to an empty
+ string (the default), the shell command specified in
+ <xref linkend="guc-archive-cleanup-command"/> is used. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>archive_cleanup_library</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for cleanup.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set at server start.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3971,39 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>recovery_end_library</varname> is set to an empty string. If
+ both <varname>recovery_end_command</varname> and
+ <varname>recovery_end_library</varname> are set, an error will be
+ raised.
</para>
</listitem>
</varlistentry>
+ <varlistentry id="guc-recovery-end-library" xreflabel="recovery_end_library">
+ <term><varname>recovery_end_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>recovery_end_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ This optional parameter specifies a library that will be executed once
+ only at the end of recovery. The purpose of this parameter is to
+ provide a mechanism for cleanup following replication or recovery. If
+ this parameter is set to an empty string (the default), the shell
+ command specified in <xref linkend="guc-recovery-end-command"/> is
+ used. If both <varname>recovery_end_command</varname> and
+ <varname>recovery_end_library</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for cleanup.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set at server start.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..963d12e02a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> or
+ <xref linkend="guc-archive-cleanup-library"/> parameter to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 073e709e06..124a0bbdb6 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -22,6 +23,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static char *BuildCleanupCommand(const char *command,
const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
@@ -29,6 +34,16 @@ static bool ExecuteRecoveryCommand(const char *command,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -73,7 +88,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(archiveCleanupCommand,
@@ -84,7 +99,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(recoveryEndCommand,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 32225be4a5..6365635180 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4883,10 +4883,11 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end library, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (recoveryEndCommand[0] != '\0' || recoveryEndLibrary[0] != '\0')
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup library, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (archiveCleanupCommand[0] != '\0' || archiveCleanupLibrary[0] != '\0')
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags)
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 50b0d1105d..574af1494e 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,15 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
+static void LoadRecoveryCallbacks(const char *cmd, const char *cmd_name,
+ const char *lib, const char *lib_name,
+ RecoveryModuleCallbacks *cb);
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +82,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (recoveryRestoreCommand[0] == '\0' && recoveryRestoreLibrary[0] == '\0')
goto not_available;
/*
@@ -149,14 +160,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore command/library and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +615,116 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Functions for loading recovery callbacks into the global RecoveryContext.
+ *
+ * To ensure that we only copy the necessary callbacks into the global context,
+ * we first copy them into a local context before copying the relevant one.
+ * This means that a recovery module's initialization function might be called
+ * multiple times in the same process. If a module uses this function for
+ * anything beyond returning its callback functions, it must be able to cope
+ * with multiple invocations.
+ */
+
+/*
+ * Loads all the recovery callbacks for the command/library into cb. This is
+ * intended for use by the functions below to load individual callbacks into
+ * the global RecoveryContext.
+ *
+ * If both the command and library are nonempty, an ERROR will be raised.
+ * cmd_name and lib_name are the GUC names to be used for the corresponding
+ * ERROR message.
+ */
+static void
+LoadRecoveryCallbacks(const char *cmd, const char *cmd_name, const char *lib,
+ const char *lib_name, RecoveryModuleCallbacks *cb)
+{
+ RecoveryModuleInit init;
+
+ if (cmd[0] != '\0' && lib[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", cmd_name, lib_name),
+ errdetail("Only one of %s, %s may be set.",
+ cmd_name, lib_name)));
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (lib[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(lib, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ (*init) (cb);
+}
+
+/*
+ * Loads only the restore callback into the global RecoveryContext.
+ */
+void
+LoadRestoreLibrary(void)
+{
+ RecoveryModuleCallbacks tmp = {0};
+
+ LoadRecoveryCallbacks(recoveryRestoreCommand, "restore_command",
+ recoveryRestoreLibrary, "restore_library",
+ &tmp);
+
+ if (tmp.restore_cb == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules used for \"restore_library\" must "
+ "register a restore callback")));
+ else
+ RecoveryContext.restore_cb = tmp.restore_cb;
+}
+
+/*
+ * Loads only the archive-cleanup callback into the global RecoveryContext.
+ */
+void
+LoadArchiveCleanupLibrary(void)
+{
+ RecoveryModuleCallbacks tmp = {0};
+
+ LoadRecoveryCallbacks(archiveCleanupCommand, "archive_cleanup_command",
+ archiveCleanupLibrary, "archive_cleanup_library",
+ &tmp);
+
+ if (tmp.archive_cleanup_cb == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules used for \"archive_cleanup_library\" "
+ "must register an archive cleanup callback")));
+ else
+ RecoveryContext.archive_cleanup_cb = tmp.archive_cleanup_cb;
+}
+
+/*
+ * Loads only the recovery-end callback into the global RecoveryContext.
+ */
+void
+LoadRecoveryEndLibrary(void)
+{
+ RecoveryModuleCallbacks tmp = {0};
+
+ LoadRecoveryCallbacks(recoveryEndCommand, "recovery_end_command",
+ recoveryEndLibrary, "recovery_end_library",
+ &tmp);
+
+ if (tmp.recovery_end_cb == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules used for \"recovery_end_library\" "
+ "must register a recovery end callback")));
+ else
+ RecoveryContext.recovery_end_cb = tmp.recovery_end_cb;
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index d5a81f9d83..d140054627 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,8 +80,11 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *recoveryRestoreLibrary = NULL;
char *recoveryEndCommand = NULL;
+char *recoveryEndLibrary = NULL;
char *archiveCleanupCommand = NULL;
+char *archiveCleanupLibrary = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
bool recoveryTargetInclusive = true;
int recoveryTargetAction = RECOVERY_TARGET_ACTION_PAUSE;
@@ -1059,20 +1062,27 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ recoveryRestoreCommand[0] == '\0' && recoveryRestoreLibrary[0] == '\0')
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (recoveryRestoreCommand[0] == '\0' && recoveryRestoreLibrary[0] == '\0')
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or restore_library when standby mode is not enabled")));
}
+ /*
+ * Load the restore and recovery end libraries. This also checks for
+ * invalid combinations of the command/library parameters.
+ */
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ LoadRestoreLibrary();
+ LoadRecoveryEndLibrary();
+
/*
* Override any inconsistent requests. Note that this is a change of
* behaviour in 9.5; prior to this we simply ignored a request to pause if
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5fc076fc14..9b479b41b4 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,15 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Load the archive cleanup library. This also checks that at most one of
+ * archive_cleanup_command, archive_cleanup_library is set. We do this
+ * before setting up the exception handler so that any problems result in a
+ * server crash shortly after startup.
+ */
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ LoadArchiveCleanupLibrary();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -563,6 +573,19 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * Since archive_cleanup_command can be changed at runtime, we have to
+ * validate that only one of the library/command is set after every
+ * SIGHUP. Failing recovery seems harsh, so we just warn that the
+ * shell command will be ignored.
+ */
+ if (archiveCleanupCommand[0] != '\0' && archiveCleanupLibrary[0] != '\0')
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both archive_cleanup_command and archive_cleanup_library set"),
+ errdetail("The value of archive_cleanup_command will be ignored."),
+ errhint("Only one of archive_cleanup_command, archive_cleanup_library may be set.")));
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f99186eab7..5dc830e6c0 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -133,7 +133,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check restore_command/library and
+ * recovery_end_command/library.
*/
static void
StartupRereadConfig(void)
@@ -161,6 +162,25 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * Since restore_command and recovery_end_command can be changed at runtime,
+ * we have to validate that only one of the library/command is set after
+ * every SIGHUP. Failing recovery seems harsh, so we just warn that the
+ * shell command will be ignored.
+ */
+ if (recoveryRestoreCommand[0] != '\0' && recoveryRestoreLibrary[0] != '\0')
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both restore_command and restore_library set"),
+ errdetail("The value of restore_command will be ignored."),
+ errhint("Only one of restore_command, restore_library may be set.")));
+ if (recoveryEndCommand[0] != '\0' && recoveryEndLibrary[0] != '\0')
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both recovery_end_command and recovery_end_library set"),
+ errdetail("The value of recovery_end_command will be ignored."),
+ errhint("Only one of recovery_end_command, recovery_end_library may be set.")));
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index a37c9f9844..9aacb584f9 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3764,6 +3764,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called to retrieve an archived WAL file."),
+ gettext_noop("An empty string indicates that \"restore_command\" should be used.")
+ },
+ &recoveryRestoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
@@ -3774,6 +3784,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"archive_cleanup_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be executed at every restart point."),
+ gettext_noop("An empty string indicates that \"archive_cleanup_command\" should be used.")
+ },
+ &archiveCleanupLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"recovery_end_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed once at the end of recovery."),
@@ -3784,6 +3804,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"recovery_end_library", PGC_POSTMASTER, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be executed once at the end of recovery."),
+ gettext_noop("An empty string indicates that \"recovery_end_command\" should be used.")
+ },
+ &recoveryEndLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"recovery_target_timeline", PGC_POSTMASTER, WAL_RECOVERY_TARGET,
gettext_noop("Specifies the timeline to recover into."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5afdeb04de..13aa10b87e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,8 +269,11 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use to restore an archived WAL file
#archive_cleanup_command = '' # command to execute at every restartpoint
+#archive_cleanup_library = '' # library to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
+#recovery_end_library = '' # library to execute at completion of recovery
# - Recovery Target -
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index e5fc66966b..467f95962b 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *recoveryRestoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 69d002cdeb..c693d200c1 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,44 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRestoreLibrary(void);
+extern void LoadArchiveCleanupLibrary(void);
+extern void LoadRecoveryEndLibrary(void);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index f3398425d8..f73fb12605 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,8 +55,11 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *recoveryRestoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
+extern PGDLLIMPORT char *recoveryEndLibrary;
extern PGDLLIMPORT char *archiveCleanupCommand;
+extern PGDLLIMPORT char *archiveCleanupLibrary;
/* indirectly set via GUC system */
extern PGDLLIMPORT TransactionId recoveryTargetXid;
--
2.25.1
Hi,
On 2022-12-27 11:24:49 -0800, Nathan Bossart wrote:
I've attached a patch set that adds the restore_library,
archive_cleanup_library, and recovery_end_library parameters to allow
archive recovery via loadable modules. This is a follow-up to the
archive_library parameter added in v15 [0] [1].
Why do we need N parameters for this? To me it seems more sensible to have one
parameter that then allows a library to implement all these (potentially
optionally).
* Unlike archive modules, recovery libraries cannot be changed at runtime.
There isn't a safe way to unload a library, and archive libraries work
around this restriction by restarting the archiver process. Since recovery
libraries are loaded via the startup and checkpointer processes (which
cannot be trivially restarted like the archiver), the same workaround is
not feasible.
I don't think that's a convincing reason to not support configuration
changes. Sure, libraries cannot be unloaded, but an unnecessarily loaded
library is cheap. All that's needed is to redirect the relevant function
calls.
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.
That seems problematic, leading to situations where one might not be able to
use restore_command anymore, because it's not feasible to do
segment-by-segment restoration.
Greetings,
Andres Freund
On Tue, Dec 27, 2022 at 02:11:11PM -0800, Andres Freund wrote:
On 2022-12-27 11:24:49 -0800, Nathan Bossart wrote:
I've attached a patch set that adds the restore_library,
archive_cleanup_library, and recovery_end_library parameters to allow
archive recovery via loadable modules. This is a follow-up to the
archive_library parameter added in v15 [0] [1].Why do we need N parameters for this? To me it seems more sensible to have one
parameter that then allows a library to implement all these (potentially
optionally).
The main reason is flexibility. Separate parameters allow using a library
for one thing and a command for another, or different libraries for
different things. If that isn't a use-case we wish to support, I don't
mind combining all three into a single recovery_library parameter.
* Unlike archive modules, recovery libraries cannot be changed at runtime.
There isn't a safe way to unload a library, and archive libraries work
around this restriction by restarting the archiver process. Since recovery
libraries are loaded via the startup and checkpointer processes (which
cannot be trivially restarted like the archiver), the same workaround is
not feasible.I don't think that's a convincing reason to not support configuration
changes. Sure, libraries cannot be unloaded, but an unnecessarily loaded
library is cheap. All that's needed is to redirect the relevant function
calls.
This might leave some stuff around (e.g., GUCs, background workers), but if
that isn't a concern, I can adjust it to work as you describe.
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.That seems problematic, leading to situations where one might not be able to
use restore_command anymore, because it's not feasible to do
segment-by-segment restoration.
I'm not following why this would make segment-by-segment restoration
infeasible. Would you mind elaborating?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Hi,
On 2022-12-27 14:37:11 -0800, Nathan Bossart wrote:
On Tue, Dec 27, 2022 at 02:11:11PM -0800, Andres Freund wrote:
On 2022-12-27 11:24:49 -0800, Nathan Bossart wrote:
I've attached a patch set that adds the restore_library,
archive_cleanup_library, and recovery_end_library parameters to allow
archive recovery via loadable modules. This is a follow-up to the
archive_library parameter added in v15 [0] [1].Why do we need N parameters for this? To me it seems more sensible to have one
parameter that then allows a library to implement all these (potentially
optionally).The main reason is flexibility. Separate parameters allow using a library
for one thing and a command for another, or different libraries for
different things. If that isn't a use-case we wish to support, I don't
mind combining all three into a single recovery_library parameter.
I think the configuration complexity is a sufficient concern to not go that
direction...
* Unlike archive modules, recovery libraries cannot be changed at runtime.
There isn't a safe way to unload a library, and archive libraries work
around this restriction by restarting the archiver process. Since recovery
libraries are loaded via the startup and checkpointer processes (which
cannot be trivially restarted like the archiver), the same workaround is
not feasible.I don't think that's a convincing reason to not support configuration
changes. Sure, libraries cannot be unloaded, but an unnecessarily loaded
library is cheap. All that's needed is to redirect the relevant function
calls.This might leave some stuff around (e.g., GUCs, background workers), but if
that isn't a concern, I can adjust it to work as you describe.
You can still have a shutdown hook re background workers. I don't think the
GUCs matter, given that it's the startup/checkpointer processes.
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.That seems problematic, leading to situations where one might not be able to
use restore_command anymore, because it's not feasible to do
segment-by-segment restoration.I'm not following why this would make segment-by-segment restoration
infeasible. Would you mind elaborating?
Latency effects for example can make it infeasible to do segment-by-segment
restoration infeasible performance wise. On the most extreme end, imagine WAL
archived to tape or such.
Greetings,
Andres Freund
On Tue, Dec 27, 2022 at 02:45:30PM -0800, Andres Freund wrote:
On 2022-12-27 14:37:11 -0800, Nathan Bossart wrote:
On Tue, Dec 27, 2022 at 02:11:11PM -0800, Andres Freund wrote:
On 2022-12-27 11:24:49 -0800, Nathan Bossart wrote:
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.That seems problematic, leading to situations where one might not be able to
use restore_command anymore, because it's not feasible to do
segment-by-segment restoration.I'm not following why this would make segment-by-segment restoration
infeasible. Would you mind elaborating?Latency effects for example can make it infeasible to do segment-by-segment
restoration infeasible performance wise. On the most extreme end, imagine WAL
archived to tape or such.
I'm sorry, I'm still lost here. Wouldn't restoration via library tend to
improve latency? Is your point that clusters may end up depending on this
improvement so much that a shell command would no longer be able to keep
up? I might be creating a straw man, but this seems like less of a concern
for pg_rewind since it isn't meant for continuous, ongoing restoration.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Tue, Dec 27, 2022 at 02:11:11PM -0800, Andres Freund wrote:
On 2022-12-27 11:24:49 -0800, Nathan Bossart wrote:
* Unlike archive modules, recovery libraries cannot be changed at runtime.
There isn't a safe way to unload a library, and archive libraries work
around this restriction by restarting the archiver process. Since recovery
libraries are loaded via the startup and checkpointer processes (which
cannot be trivially restarted like the archiver), the same workaround is
not feasible.I don't think that's a convincing reason to not support configuration
changes. Sure, libraries cannot be unloaded, but an unnecessarily loaded
library is cheap. All that's needed is to redirect the relevant function
calls.
Agreed. That seems worth the cost to switching this stuff to be
SIGHUP-able.
* pg_rewind uses restore_command, but there isn't a straightforward path to
support restore_library. I haven't addressed this in the attached patches,
but perhaps this is a reason to allow specifying both restore_command and
restore_library at the same time. pg_rewind would use restore_command, and
the server would use restore_library.That seems problematic, leading to situations where one might not be able to
use restore_command anymore, because it's not feasible to do
segment-by-segment restoration.
Do you mean that supporting restore_library in pg_rewind is a hard
requirement? I fail to see why this should be the case. Note that
having the possibility to do dlopen() calls in the frontend (aka
libpq) for loading some callbacks is something I've been looking for
in the past. Having consistency in terms of restrictions between
library and command like for archives would make sense I guess (FWIW,
I mentioned on the thread of d627ce3 that we'd better not put any
restrictions for the archives).
--
Michael
Hi,
On 2022-12-27 15:04:28 -0800, Nathan Bossart wrote:
I'm sorry, I'm still lost here. Wouldn't restoration via library tend to
improve latency? Is your point that clusters may end up depending on this
improvement so much that a shell command would no longer be able to keep
up?
Yes.
I might be creating a straw man, but this seems like less of a concern
for pg_rewind since it isn't meant for continuous, ongoing restoration.
pg_rewind is in the critical path of a bunch of HA scenarios, so I wouldn't
say that restore performance isn't important...
Greetings,
Andres Freund
Here is a new patch set with the following changes:
* The restore_library, archive_cleanup_library, and recovery_end_library
parameters are merged into one parameter.
* restore_library can now be changed via SIGHUP. To provide a way for
modules to clean up when their callbacks are unloaded, I've introduced an
optional shutdown callback.
* Parameter misconfigurations are now always ERRORs. I'm less confident
that we can get by with just a WARNING now that restore_library can be
changed via SIGHUP, and this makes things more consistent with
archive_library/command.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From e46a2ab6320de66ad41df13df2fa57e9e9bc76e2 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v2 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 194 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 44 ++++-
src/backend/access/transam/xlogarchive.c | 158 +----------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 240 insertions(+), 165 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 65c77531be..a0870217b8 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..3ddcabd969
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+ if (cmd == NULL)
+ elog(ERROR, "could not build restore command \"%s\"", cmd);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char xlogRecoveryCmd[MAXPGPATH];
+ char *dp;
+ char *endp;
+ const char *sp;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ dp = xlogRecoveryCmd;
+ endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ *endp = '\0';
+
+ for (sp = command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'r':
+ /* %r: filename of last restartpoint */
+ sp++;
+ strlcpy(dp, lastRestartPointFileName, endp - dp);
+ dp += strlen(dp);
+ break;
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ }
+ }
+ else
+ {
+ if (dp < endp)
+ *dp++ = *sp;
+ }
+ }
+ *dp = '\0';
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 91473b00d9..32225be4a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,10 +4887,24 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7321,24 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index e2b7176f2f..50b0d1105d 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -56,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -149,18 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
- if (xlogRestoreCmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"",
- recoveryRestoreCommand);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -169,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -233,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -277,110 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char xlogRecoveryCmd[MAXPGPATH];
- char lastRestartPointFname[MAXPGPATH];
- char *dp;
- char *endp;
- const char *sp;
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
- *endp = '\0';
-
- for (sp = command; *sp; sp++)
- {
- if (*sp == '%')
- {
- switch (sp[1])
- {
- case 'r':
- /* %r: filename of last restartpoint */
- sp++;
- strlcpy(dp, lastRestartPointFname, endp - dp);
- dp += strlen(dp);
- break;
- case '%':
- /* convert %% to a single % */
- sp++;
- if (dp < endp)
- *dp++ = *sp;
- break;
- default:
- /* otherwise treat the % as not special */
- if (dp < endp)
- *dp++ = *sp;
- break;
- }
- }
- else
- {
- if (dp < endp)
- *dp++ = *sp;
- }
- }
- *dp = '\0';
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index f47b219538..69d002cdeb 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v2-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 4eb6c201f3b95bab2cd855aa456042bf343db366 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v2 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 108 +++++++++++----------
1 file changed, 58 insertions(+), 50 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 3ddcabd969..073e709e06 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -22,17 +22,19 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static char *BuildCleanupCommand(const char *command,
+ const char *lastRestartPointFileName);
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
@@ -40,19 +42,6 @@ shell_restore(const char *file, const char *path,
if (cmd == NULL)
elog(ERROR, "could not build restore command \"%s\"", cmd);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -77,60 +66,52 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(archiveCleanupCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(recoveryEndCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Build a recovery_end_command or archive_cleanup_command. The return value
+ * is palloc'd.
*/
-static void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+static char *
+BuildCleanupCommand(const char *command, const char *lastRestartPointFileName)
{
- char xlogRecoveryCmd[MAXPGPATH];
+ char *ret = (char *) palloc(MAXPGPATH);
char *dp;
char *endp;
const char *sp;
- int rc;
-
- Assert(command && commandName);
/*
* construct the command to be executed
*/
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ dp = ret;
+ endp = ret + MAXPGPATH - 1;
*endp = '\0';
for (sp = command; *sp; sp++)
@@ -166,6 +147,28 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
}
*dp = '\0';
+ return ret;
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
+ *
+ * Returns whether the command succeeded.
+ */
+static bool
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
+{
+ int rc;
+
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -174,16 +177,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -191,4 +197,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v2-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From cbc41de2158d165e90d322f2f80a007e8c3f41e9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v2 3/3] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 42 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 616 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 9f221816bb..d6ed996f6c 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index b25dce99a3..5b5a6d79e7 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..86ff0dc7f5
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..d6d34078fc 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 0b650f17a8..ac7cd9b967 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2>
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 05b3862d09..899d2b5fdb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 073e709e06..124a0bbdb6 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -22,6 +23,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static char *BuildCleanupCommand(const char *command,
const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
@@ -29,6 +34,16 @@ static bool ExecuteRecoveryCommand(const char *command,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -73,7 +88,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(archiveCleanupCommand,
@@ -84,7 +99,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(recoveryEndCommand,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 32225be4a5..2179e44386 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4883,10 +4883,11 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags)
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 50b0d1105d..4caecfaea0 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index d5a81f9d83..f1c0a4ffad 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5fc076fc14..bfa6e4f0d0 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index fffb6a599c..623637d8e1 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f99186eab7..14701b2b85 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c6326d5053..5213b2b0b1 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index a37c9f9844..8796bcf7ca 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3764,6 +3764,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5afdeb04de..e71e79271a 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index e5fc66966b..133a614133 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 69d002cdeb..090e92e554 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index f3398425d8..86a41ff35d 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 91cc341854..d1445cdfcb 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
Here is a rebased patch set for cfbot.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v3-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From 7fecc9c9dc8a0ebbfbb1828a8410dac1be1ce7f5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v3 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 194 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 44 ++++-
src/backend/access/transam/xlogarchive.c | 158 +----------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 240 insertions(+), 165 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 8920c1bfce..3031c2f6cf 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..3ddcabd969
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+ if (cmd == NULL)
+ elog(ERROR, "could not build restore command \"%s\"", cmd);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char xlogRecoveryCmd[MAXPGPATH];
+ char *dp;
+ char *endp;
+ const char *sp;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ dp = xlogRecoveryCmd;
+ endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ *endp = '\0';
+
+ for (sp = command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'r':
+ /* %r: filename of last restartpoint */
+ sp++;
+ strlcpy(dp, lastRestartPointFileName, endp - dp);
+ dp += strlen(dp);
+ break;
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ }
+ }
+ else
+ {
+ if (dp < endp)
+ *dp++ = *sp;
+ }
+ }
+ *dp = '\0';
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0070d56b0b..fdce12614a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,10 +4887,24 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7321,24 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 76abc74c67..b5cb060d55 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -56,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -149,18 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
- if (xlogRestoreCmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"",
- recoveryRestoreCommand);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -169,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -233,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -277,110 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char xlogRecoveryCmd[MAXPGPATH];
- char lastRestartPointFname[MAXPGPATH];
- char *dp;
- char *endp;
- const char *sp;
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
- *endp = '\0';
-
- for (sp = command; *sp; sp++)
- {
- if (*sp == '%')
- {
- switch (sp[1])
- {
- case 'r':
- /* %r: filename of last restartpoint */
- sp++;
- strlcpy(dp, lastRestartPointFname, endp - dp);
- dp += strlen(dp);
- break;
- case '%':
- /* convert %% to a single % */
- sp++;
- if (dp < endp)
- *dp++ = *sp;
- break;
- default:
- /* otherwise treat the % as not special */
- if (dp < endp)
- *dp++ = *sp;
- break;
- }
- }
- else
- {
- if (dp < endp)
- *dp++ = *sp;
- }
- }
- *dp = '\0';
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..299304703e 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v3-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 6c1ce4710dbad27f230c68e4089d4faac8e5f385 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v3 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 108 +++++++++++----------
1 file changed, 58 insertions(+), 50 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 3ddcabd969..073e709e06 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -22,17 +22,19 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static char *BuildCleanupCommand(const char *command,
+ const char *lastRestartPointFileName);
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
@@ -40,19 +42,6 @@ shell_restore(const char *file, const char *path,
if (cmd == NULL)
elog(ERROR, "could not build restore command \"%s\"", cmd);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -77,60 +66,52 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(archiveCleanupCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(recoveryEndCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Build a recovery_end_command or archive_cleanup_command. The return value
+ * is palloc'd.
*/
-static void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+static char *
+BuildCleanupCommand(const char *command, const char *lastRestartPointFileName)
{
- char xlogRecoveryCmd[MAXPGPATH];
+ char *ret = (char *) palloc(MAXPGPATH);
char *dp;
char *endp;
const char *sp;
- int rc;
-
- Assert(command && commandName);
/*
* construct the command to be executed
*/
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ dp = ret;
+ endp = ret + MAXPGPATH - 1;
*endp = '\0';
for (sp = command; *sp; sp++)
@@ -166,6 +147,28 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
}
*dp = '\0';
+ return ret;
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
+ *
+ * Returns whether the command succeeded.
+ */
+static bool
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
+{
+ int rc;
+
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -174,16 +177,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -191,4 +197,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v3-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From f566c97320b958d306c38b6c9869d2e62700f295 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v3 3/3] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 42 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 616 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..86ff0dc7f5
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,42 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..d6d34078fc 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 0b650f17a8..ac7cd9b967 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2>
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 05b3862d09..899d2b5fdb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 073e709e06..124a0bbdb6 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -22,6 +23,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static char *BuildCleanupCommand(const char *command,
const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
@@ -29,6 +34,16 @@ static bool ExecuteRecoveryCommand(const char *command,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -73,7 +88,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(archiveCleanupCommand,
@@ -84,7 +99,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(recoveryEndCommand,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdce12614a..c4ad571327 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4883,10 +4883,11 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags)
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..fdab7dad43 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index bc3c3eb3e7..95bcc3a0b7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 68328b1402..19746e2489 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3764,6 +3764,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5afdeb04de..e71e79271a 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Tue, Jan 03, 2023 at 09:59:17AM -0800, Nathan Bossart wrote:
Here is a rebased patch set for cfbot.
I noticed that cfbot's Windows tests are failing because the backslashes in
the archive directory path are causing escaping problems. Here is an
attempt to fix that by converting all backslashes to forward slashes, which
is what other tests (e.g., 025_stuck_on_old_timeline.pl) do.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v4-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From 7fecc9c9dc8a0ebbfbb1828a8410dac1be1ce7f5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v4 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 194 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 44 ++++-
src/backend/access/transam/xlogarchive.c | 158 +----------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 240 insertions(+), 165 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 8920c1bfce..3031c2f6cf 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..3ddcabd969
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,194 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+ if (cmd == NULL)
+ elog(ERROR, "could not build restore command \"%s\"", cmd);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char xlogRecoveryCmd[MAXPGPATH];
+ char *dp;
+ char *endp;
+ const char *sp;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ dp = xlogRecoveryCmd;
+ endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ *endp = '\0';
+
+ for (sp = command; *sp; sp++)
+ {
+ if (*sp == '%')
+ {
+ switch (sp[1])
+ {
+ case 'r':
+ /* %r: filename of last restartpoint */
+ sp++;
+ strlcpy(dp, lastRestartPointFileName, endp - dp);
+ dp += strlen(dp);
+ break;
+ case '%':
+ /* convert %% to a single % */
+ sp++;
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ default:
+ /* otherwise treat the % as not special */
+ if (dp < endp)
+ *dp++ = *sp;
+ break;
+ }
+ }
+ else
+ {
+ if (dp < endp)
+ *dp++ = *sp;
+ }
+ }
+ *dp = '\0';
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0070d56b0b..fdce12614a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,10 +4887,24 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7321,24 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 76abc74c67..b5cb060d55 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -56,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -149,18 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
- if (xlogRestoreCmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"",
- recoveryRestoreCommand);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -169,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -233,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -277,110 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char xlogRecoveryCmd[MAXPGPATH];
- char lastRestartPointFname[MAXPGPATH];
- char *dp;
- char *endp;
- const char *sp;
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
- *endp = '\0';
-
- for (sp = command; *sp; sp++)
- {
- if (*sp == '%')
- {
- switch (sp[1])
- {
- case 'r':
- /* %r: filename of last restartpoint */
- sp++;
- strlcpy(dp, lastRestartPointFname, endp - dp);
- dp += strlen(dp);
- break;
- case '%':
- /* convert %% to a single % */
- sp++;
- if (dp < endp)
- *dp++ = *sp;
- break;
- default:
- /* otherwise treat the % as not special */
- if (dp < endp)
- *dp++ = *sp;
- break;
- }
- }
- else
- {
- if (dp < endp)
- *dp++ = *sp;
- }
- }
- *dp = '\0';
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..299304703e 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v4-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 6c1ce4710dbad27f230c68e4089d4faac8e5f385 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v4 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 108 +++++++++++----------
1 file changed, 58 insertions(+), 50 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 3ddcabd969..073e709e06 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -22,17 +22,19 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static char *BuildCleanupCommand(const char *command,
+ const char *lastRestartPointFileName);
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
@@ -40,19 +42,6 @@ shell_restore(const char *file, const char *path,
if (cmd == NULL)
elog(ERROR, "could not build restore command \"%s\"", cmd);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -77,60 +66,52 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(archiveCleanupCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd = BuildCleanupCommand(recoveryEndCommand,
+ lastRestartPointFileName);
+
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Build a recovery_end_command or archive_cleanup_command. The return value
+ * is palloc'd.
*/
-static void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+static char *
+BuildCleanupCommand(const char *command, const char *lastRestartPointFileName)
{
- char xlogRecoveryCmd[MAXPGPATH];
+ char *ret = (char *) palloc(MAXPGPATH);
char *dp;
char *endp;
const char *sp;
- int rc;
-
- Assert(command && commandName);
/*
* construct the command to be executed
*/
- dp = xlogRecoveryCmd;
- endp = xlogRecoveryCmd + MAXPGPATH - 1;
+ dp = ret;
+ endp = ret + MAXPGPATH - 1;
*endp = '\0';
for (sp = command; *sp; sp++)
@@ -166,6 +147,28 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
}
*dp = '\0';
+ return ret;
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
+ *
+ * Returns whether the command succeeded.
+ */
+static bool
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
+{
+ int rc;
+
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -174,16 +177,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -191,4 +197,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v4-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From c76c39a309e03ce980fbabce53179a90b84bba37 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v4 3/3] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..d6d34078fc 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 0b650f17a8..ac7cd9b967 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2>
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 05b3862d09..899d2b5fdb 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 073e709e06..124a0bbdb6 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -22,6 +23,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static char *BuildCleanupCommand(const char *command,
const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
@@ -29,6 +34,16 @@ static bool ExecuteRecoveryCommand(const char *command,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -73,7 +88,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(archiveCleanupCommand,
@@ -84,7 +99,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd = BuildCleanupCommand(recoveryEndCommand,
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdce12614a..c4ad571327 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4883,10 +4883,11 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags)
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..fdab7dad43 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index bc3c3eb3e7..95bcc3a0b7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 68328b1402..19746e2489 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3764,6 +3764,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 5afdeb04de..e71e79271a 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Tue, Jan 03, 2023 at 11:05:38AM -0800, Nathan Bossart wrote:
I noticed that cfbot's Windows tests are failing because the backslashes in
the archive directory path are causing escaping problems. Here is an
attempt to fix that by converting all backslashes to forward slashes, which
is what other tests (e.g., 025_stuck_on_old_timeline.pl) do.
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
Hmm. Is passing down the file name used as a cutoff point the best
interface for the modules? Perhaps passing down the redo LSN and its
TLI would be a cleaner approach in terms of flexibility? I agree with
letting the startup enforce these numbers as that can be easy to mess
up for plugin authors, leading to critical problems. The same code
pattern is repeated twice for the end-of-recovery callback and the
cleanup commands when it comes to building the file name. Not
critical, still not really nice.
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
"basic_archive" does not reflect what this module does. Using one
library simplifies the whole configuration picture and the tests, so
perhaps something like basic_wal_module, or something like that, would
be better long-term?
--
Michael
Thanks for taking a look.
On Wed, Jan 11, 2023 at 04:53:39PM +0900, Michael Paquier wrote:
Hmm. Is passing down the file name used as a cutoff point the best
interface for the modules? Perhaps passing down the redo LSN and its
TLI would be a cleaner approach in terms of flexibility? I agree with
letting the startup enforce these numbers as that can be easy to mess
up for plugin authors, leading to critical problems.
I'm having trouble thinking of any practical advantage of providing the
redo LSN and TLI. If the main use-case is removing older archives as the
documentation indicates, it seems better to provide the file name so that
you can plug it straight into strcmp() to determine whether the file can be
removed (i.e., what pg_archivecleanup does). If we provided the LSN and
TLI instead, you'd either need to convert that into a WAL file name for
strcmp(), or you'd need to convert the candidate file name into an LSN and
TLI and compare against those.
"basic_archive" does not reflect what this module does. Using one
library simplifies the whole configuration picture and the tests, so
perhaps something like basic_wal_module, or something like that, would
be better long-term?
I initially created a separate basic_restore module, but decided to fold it
into basic_archive to simplify the patch and tests. I hesitated to rename
it because it already exists in v15, and since it deals with creating and
restoring archive files, the name still seemed somewhat accurate. That
being said, I don't mind renaming it if that's what folks want.
I've attached a new patch set that is rebased over c96de2c. There are no
other changes.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v5-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From 7cd6e2a870457f350d3c829fb434d9204d3814ae Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v5 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 160 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 44 ++++--
src/backend/access/transam/xlogarchive.c | 123 +---------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 206 insertions(+), 130 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 8920c1bfce..3031c2f6cf 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..a0562af95c
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+ if (cmd == NULL)
+ elog(ERROR, "could not build restore command \"%s\"", cmd);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0070d56b0b..fdce12614a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,10 +4887,24 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7321,24 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXPGPATH];
+ XLogSegNo restartSegNo;
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+
+ /*
+ * Calculate the archive file cutoff point for use during log shipping
+ * replication. All files earlier than this point can be deleted from
+ * the archive, though there is no requirement to do so.
+ */
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
+ wal_segment_size);
+
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index f911e8c3a6..b5cb060d55 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,7 +23,6 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -57,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -150,18 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
- if (xlogRestoreCmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"",
- recoveryRestoreCommand);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -170,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -234,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -278,74 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..299304703e 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v5-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 7332c197ddb4db4be17f96e60066514728e0dced Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v5 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 92 ++++++++++------------
1 file changed, 40 insertions(+), 52 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index a0562af95c..68bf6e8556 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -23,36 +23,21 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
lastRestartPointFileName);
- if (cmd == NULL)
- elog(ERROR, "could not build restore command \"%s\"", cmd);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
@@ -78,30 +63,37 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(archiveCleanupCommand,
+ "archive_cleanup_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(recoveryEndCommand,
+ "recovery_end_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -109,26 +101,19 @@ shell_recovery_end(const char *lastRestartPointFileName)
*
* 'command' is the shell command to be executed, 'commandName' is a
* human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
*
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Returns whether the command succeeded.
*/
-static void
+static bool
ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
{
- char *xlogRecoveryCmd;
- int rc;
-
- Assert(command && commandName);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
- lastRestartPointFileName);
+ int rc;
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -138,18 +123,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
- pfree(xlogRecoveryCmd);
-
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -157,4 +143,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v5-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From 60835bcaf35d6ec614f567829f79ab4d65c6ecf7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v5 3/3] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..d6d34078fc 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 60f23d2855..11fd670dbc 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 77574e2d4e..039d3360ac 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 68bf6e8556..eb101b51fb 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -23,11 +24,25 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -70,7 +85,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -83,7 +98,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fdce12614a..c4ad571327 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4883,10 +4883,11 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -4903,7 +4904,7 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7318,9 +7319,9 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXPGPATH];
XLogSegNo restartSegNo;
@@ -7337,7 +7338,7 @@ CreateRestartPoint(int flags)
XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
wal_segment_size);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..fdab7dad43 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 5e65785306..db0cd4469a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..a8a516b0c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..38fb3e0823 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Wed, Jan 11, 2023 at 11:29:01AM -0800, Nathan Bossart wrote:
I'm having trouble thinking of any practical advantage of providing the
redo LSN and TLI. If the main use-case is removing older archives as the
documentation indicates, it seems better to provide the file name so that
you can plug it straight into strcmp() to determine whether the file can be
removed (i.e., what pg_archivecleanup does). If we provided the LSN and
TLI instead, you'd either need to convert that into a WAL file name for
strcmp(), or you'd need to convert the candidate file name into an LSN and
TLI and compare against those.
Logging was one thing that came immediately in mind, to let the module
know the redo LSN and TLI the segment name was built from without
having to recalculate it back. If you don't feel strongly about that,
I am fine to discard this remark. It is not like this hook should be
set in stone across major releases, in any case.
I initially created a separate basic_restore module, but decided to fold it
into basic_archive to simplify the patch and tests. I hesitated to rename
it because it already exists in v15, and since it deals with creating and
restoring archive files, the name still seemed somewhat accurate. That
being said, I don't mind renaming it if that's what folks want.
I've done that in the past for pg_verify_checksums -> pg_checksums, so
I would not mind renaming it so as it reflects better its role.
(Being outvoted is fine for me if this suggestion sounds bad).
Saying that, 0001 seems fine on its own (minus the redo LSN/TLI with
the duplication for the segment name build), so I would be tempted to
get this one done. My gut tells me that we'd better remove the
duplication and just pass down the two fields to
shell_archive_cleanup() and shell_recovery_end(), with the segment
name given to ExecuteRecoveryCommand()..
--
Michael
On Thu, Jan 12, 2023 at 03:30:40PM +0900, Michael Paquier wrote:
On Wed, Jan 11, 2023 at 11:29:01AM -0800, Nathan Bossart wrote:
I initially created a separate basic_restore module, but decided to fold it
into basic_archive to simplify the patch and tests. I hesitated to rename
it because it already exists in v15, and since it deals with creating and
restoring archive files, the name still seemed somewhat accurate. That
being said, I don't mind renaming it if that's what folks want.I've done that in the past for pg_verify_checksums -> pg_checksums, so
I would not mind renaming it so as it reflects better its role.
(Being outvoted is fine for me if this suggestion sounds bad).
IMHO I don't think there's an urgent need to rename it, but if there's a
better name that people like, I'm happy to do so.
Saying that, 0001 seems fine on its own (minus the redo LSN/TLI with
the duplication for the segment name build), so I would be tempted to
get this one done. My gut tells me that we'd better remove the
duplication and just pass down the two fields to
shell_archive_cleanup() and shell_recovery_end(), with the segment
name given to ExecuteRecoveryCommand()..
I moved the duplicated logic to its own function in v6.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v6-0001-Move-the-code-to-restore-files-via-the-shell-to-a.patchtext/x-diff; charset=us-asciiDownload
From 078e81ce389680fb98a8b5c2fb6cfb3fb42f892e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:35:25 -0800
Subject: [PATCH v6 1/3] Move the code to restore files via the shell to a
separate file.
This is preparatory work for allowing more extensibility in this
area.
---
src/backend/access/transam/Makefile | 1 +
src/backend/access/transam/meson.build | 1 +
src/backend/access/transam/shell_restore.c | 158 +++++++++++++++++++++
src/backend/access/transam/xlog.c | 37 +++--
src/backend/access/transam/xlogarchive.c | 120 +---------------
src/include/access/xlogarchive.h | 7 +-
6 files changed, 197 insertions(+), 127 deletions(-)
create mode 100644 src/backend/access/transam/shell_restore.c
diff --git a/src/backend/access/transam/Makefile b/src/backend/access/transam/Makefile
index 661c55a9db..099c315d03 100644
--- a/src/backend/access/transam/Makefile
+++ b/src/backend/access/transam/Makefile
@@ -19,6 +19,7 @@ OBJS = \
multixact.o \
parallel.o \
rmgr.o \
+ shell_restore.o \
slru.o \
subtrans.o \
timeline.o \
diff --git a/src/backend/access/transam/meson.build b/src/backend/access/transam/meson.build
index 8920c1bfce..3031c2f6cf 100644
--- a/src/backend/access/transam/meson.build
+++ b/src/backend/access/transam/meson.build
@@ -7,6 +7,7 @@ backend_sources += files(
'multixact.c',
'parallel.c',
'rmgr.c',
+ 'shell_restore.c',
'slru.c',
'subtrans.c',
'timeline.c',
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
new file mode 100644
index 0000000000..f52f0b92a4
--- /dev/null
+++ b/src/backend/access/transam/shell_restore.c
@@ -0,0 +1,158 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These recovery functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2022, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/access/transam/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName, bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+bool
+shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+ rc = system(cmd);
+ pgstat_report_wait_end();
+
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+
+ return (rc == 0);
+}
+
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0070d56b0b..8f47fb7570 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -692,6 +692,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4887,10 +4888,12 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
* Execute the recovery_end_command, if any.
*/
if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,10 +7310,12 @@ CreateRestartPoint(int flags)
* Finally, execute archive_cleanup_command, if any.
*/
if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -8884,6 +8889,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..b5cb060d55 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,7 +23,6 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -57,9 +56,8 @@ RestoreArchivedFile(char *path, const char *xlogfname,
bool cleanupEnabled)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -150,15 +148,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
/*
* Check signals before restore command and reset afterwards.
*/
@@ -167,15 +156,11 @@ RestoreArchivedFile(char *path, const char *xlogfname,
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
+ ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
PostRestoreCommand();
- pfree(xlogRestoreCmd);
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -231,37 +216,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -275,74 +229,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..299304703e 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -20,8 +20,6 @@
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
bool cleanupEnabled);
-extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
extern void XLogArchiveNotify(const char *xlog);
extern void XLogArchiveNotifySeg(XLogSegNo segno, TimeLineID tli);
@@ -32,4 +30,9 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
+extern bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
#endif /* XLOG_ARCHIVE_H */
--
2.25.1
v6-0002-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 0ea2d9f96876c2398b0b8517bfec8d8b8f41518f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v6 2/3] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 90 ++++++++++------------
1 file changed, 40 insertions(+), 50 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index f52f0b92a4..68bf6e8556 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -23,35 +23,22 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool exitOnSigterm, uint32 wait_event_info,
+ int fail_elevel);
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
lastRestartPointFileName);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -76,30 +63,37 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(archiveCleanupCommand,
+ "archive_cleanup_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(recoveryEndCommand,
+ "recovery_end_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -107,26 +101,19 @@ shell_recovery_end(const char *lastRestartPointFileName)
*
* 'command' is the shell command to be executed, 'commandName' is a
* human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
*
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Returns whether the command succeeded.
*/
-static void
+static bool
ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
{
- char *xlogRecoveryCmd;
- int rc;
-
- Assert(command && commandName);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
- lastRestartPointFileName);
+ int rc;
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -136,18 +123,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
- pfree(xlogRecoveryCmd);
-
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -155,4 +143,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v6-0003-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From 6ae526ded564d1b67792171d4f073ce3a97e57c3 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v6 3/3] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8bab521718..d6d34078fc 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1181,9 +1181,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1202,14 +1220,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1233,7 +1257,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 60f23d2855..11fd670dbc 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 77574e2d4e..039d3360ac 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 68bf6e8556..eb101b51fb 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These recovery functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own recovery logic.
*
* Copyright (c) 2022, PostgreSQL Global Development Group
*
@@ -23,11 +24,25 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName, bool failOnSignal,
bool exitOnSigterm, uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
@@ -70,7 +85,7 @@ shell_restore(const char *file, const char *path,
return ret;
}
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -83,7 +98,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
pfree(cmd);
}
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8f47fb7570..ae537cd87f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4884,15 +4884,16 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7307,14 +7308,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..fdab7dad43 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 5e65785306..db0cd4469a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..a8a516b0c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..38fb3e0823 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Thu, Jan 12, 2023 at 10:17:21AM -0800, Nathan Bossart wrote:
On Thu, Jan 12, 2023 at 03:30:40PM +0900, Michael Paquier wrote:
IMHO I don't think there's an urgent need to rename it, but if there's a
better name that people like, I'm happy to do so.
Okay.
Saying that, 0001 seems fine on its own (minus the redo LSN/TLI with
the duplication for the segment name build), so I would be tempted to
get this one done. My gut tells me that we'd better remove the
duplication and just pass down the two fields to
shell_archive_cleanup() and shell_recovery_end(), with the segment
name given to ExecuteRecoveryCommand()..I moved the duplicated logic to its own function in v6.
While looking at 0001, I have noticed one issue as of the following
block in shell_restore():
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
This block of code would have been executed even if rc == 0, which is
incorrect because the command would have succeeded. HEAD would not
have done that, actually, as RestoreArchivedFile() would return
before. I guess that you have not noticed it because this basically
just generated incorrect DEBUG2 messages when rc == 0?
One part that this slightly influences is the order of the reports
when the command succeeds the follow-up stat() fails to check the
size, where we reverse these two logs:
DEBUG2, "could not restore"
LOG/FATAL, "could not stat file"
However, that does not really change the value of the information
reported: if the stat() failed, the failure mode is the same except
that we don't get the extra DEBUG2/"could not restore" message, which
does not really matter except if your elevel is high enough for
that and there is always the LOG for that..
Once this issue was fixed, nothing else stood out, so applied this
part.
--
Michael
On Mon, Jan 16, 2023 at 04:36:01PM +0900, Michael Paquier wrote:
Once this issue was fixed, nothing else stood out, so applied this
part.
Thanks! I've attached a rebased version of the rest of the patch set.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v7-0001-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From c2e6eb1251b47743c50717cad9adc49ccd7249d5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v7 1/2] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 90 ++++++++++------------
1 file changed, 39 insertions(+), 51 deletions(-)
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 7753a7d667..f5b6cf174e 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -25,11 +25,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
- bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel);
/*
* Attempt to execute a shell-based restore command.
@@ -41,25 +40,12 @@ shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
lastRestartPointFileName);
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
-
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
* just part of the process... that makes it difficult to determine
@@ -84,17 +70,11 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (rc != 0)
- {
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
- }
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
/*
@@ -103,9 +83,14 @@ shell_restore(const char *file, const char *path,
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(archiveCleanupCommand,
+ "archive_cleanup_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -114,9 +99,14 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(recoveryEndCommand,
+ "recovery_end_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -124,27 +114,22 @@ shell_recovery_end(const char *lastRestartPointFileName)
*
* 'command' is the shell command to be executed, 'commandName' is a
* human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
*
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Returns whether the command succeeded.
*/
-static void
+static bool
ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
{
- char *xlogRecoveryCmd;
int rc;
Assert(command && commandName);
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
- lastRestartPointFileName);
-
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -153,18 +138,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
- pfree(xlogRecoveryCmd);
-
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -172,4 +158,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
--
2.25.1
v7-0002-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From 1fcf18db8c32d655eb50c9531fe343330a4c0593 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v7 2/2] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..f44135061d 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 60f23d2855..11fd670dbc 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 77574e2d4e..039d3360ac 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index f5b6cf174e..c0bc78d8b4 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -4,7 +4,8 @@
* Recovery functions for a user-specified shell command.
*
* These recovery functions use a user-specified shell command (e.g. based
- * on the GUC restore_command).
+ * on the GUC restore_command). It is used as the default, but other
+ * modules may define their own recovery logic.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -25,11 +26,25 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal, bool exitOnSigterm,
uint32 wait_event_info, int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
/*
* Attempt to execute a shell-based restore command.
*
@@ -80,7 +95,7 @@ shell_restore(const char *file, const char *path,
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -96,7 +111,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8f47fb7570..ae537cd87f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4884,15 +4884,16 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7307,14 +7308,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..fdab7dad43 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,9 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -32,6 +34,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -71,7 +78,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -149,14 +156,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -603,3 +611,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 5e65785306..db0cd4469a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..a8a516b0c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..38fb3e0823 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Mon, Jan 16, 2023 at 02:40:40PM -0800, Nathan Bossart wrote:
On Mon, Jan 16, 2023 at 04:36:01PM +0900, Michael Paquier wrote:
Once this issue was fixed, nothing else stood out, so applied this
part.Thanks! I've attached a rebased version of the rest of the patch set.
When it comes to 0002, the only difference between the three code
paths of shell_recovery_end(), shell_archive_cleanup() and
shell_restore() is the presence of BuildRestoreCommand(). However
this is now just a thin wrapper of replace_percent_placeholders() that
does just one extra make_native_path() for the xlogpath.
Could it be cleaner in the long term to remove entirely
BuildRestoreCommand() and move the conversion of the xlogpath with
make_native_path() one level higher in the stack?
--
Michael
On Tue, Jan 17, 2023 at 02:32:03PM +0900, Michael Paquier wrote:
Could it be cleaner in the long term to remove entirely
BuildRestoreCommand() and move the conversion of the xlogpath with
make_native_path() one level higher in the stack?
Yeah, this seems cleaner. I removed BuildRestoreCommand() in v8.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v8-0001-Refactor-code-for-restoring-files-via-shell.patchtext/x-diff; charset=us-asciiDownload
From c710f5a9e294b198ce6bb2e8d9404cb26a76b913 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 23 Dec 2022 16:53:38 -0800
Subject: [PATCH v8 1/2] Refactor code for restoring files via shell.
Presently, restore_command uses a different code path than
archive_cleanup_command and recovery_end_command. These code paths
are similar and can be easily combined.
---
src/backend/access/transam/shell_restore.c | 99 ++++++++++------------
src/backend/access/transam/xlogarchive.c | 1 -
src/common/Makefile | 1 -
src/common/archive.c | 60 -------------
src/common/meson.build | 1 -
src/common/percentrepl.c | 13 +--
src/fe_utils/archive.c | 11 ++-
src/include/common/archive.h | 21 -----
src/tools/msvc/Mkvcbuild.pm | 2 +-
9 files changed, 56 insertions(+), 153 deletions(-)
delete mode 100644 src/common/archive.c
delete mode 100644 src/include/common/archive.h
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 7753a7d667..4752be0b9f 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -20,16 +20,14 @@
#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
-#include "common/archive.h"
#include "common/percentrepl.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
-static void ExecuteRecoveryCommand(const char *command,
+static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
- bool failOnSignal,
- uint32 wait_event_info,
- const char *lastRestartPointFileName);
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel);
/*
* Attempt to execute a shell-based restore command.
@@ -40,25 +38,16 @@ bool
shell_restore(const char *file, const char *path,
const char *lastRestartPointFileName)
{
+ char *nativePath = pstrdup(path);
char *cmd;
- int rc;
+ bool ret;
/* Build the restore command to execute */
- cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
- lastRestartPointFileName);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"", cmd)));
-
- /*
- * Copy xlog from archival storage to XLOGDIR
- */
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
- rc = system(cmd);
- pgstat_report_wait_end();
-
- pfree(cmd);
+ make_native_path(nativePath);
+ cmd = replace_percent_placeholders(recoveryRestoreCommand,
+ "restore_command", "frp", file,
+ lastRestartPointFileName, nativePath);
+ pfree(nativePath);
/*
* Remember, we rollforward UNTIL the restore fails so failure here is
@@ -84,17 +73,11 @@ shell_restore(const char *file, const char *path,
*
* We treat hard shell errors such as "command not found" as fatal, too.
*/
- if (rc != 0)
- {
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- file, wait_result_to_str(rc))));
- }
+ ret = ExecuteRecoveryCommand(cmd, "restore_command", true, true,
+ WAIT_EVENT_RESTORE_COMMAND, DEBUG2);
+ pfree(cmd);
- return (rc == 0);
+ return ret;
}
/*
@@ -103,9 +86,14 @@ shell_restore(const char *file, const char *path,
void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
- false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(archiveCleanupCommand,
+ "archive_cleanup_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "archive_cleanup_command", false, false,
+ WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -114,9 +102,14 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
void
shell_recovery_end(const char *lastRestartPointFileName)
{
- ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
- WAIT_EVENT_RECOVERY_END_COMMAND,
- lastRestartPointFileName);
+ char *cmd;
+
+ cmd = replace_percent_placeholders(recoveryEndCommand,
+ "recovery_end_command",
+ "r", lastRestartPointFileName);
+ (void) ExecuteRecoveryCommand(cmd, "recovery_end_command", true, false,
+ WAIT_EVENT_RECOVERY_END_COMMAND, WARNING);
+ pfree(cmd);
}
/*
@@ -124,27 +117,22 @@ shell_recovery_end(const char *lastRestartPointFileName)
*
* 'command' is the shell command to be executed, 'commandName' is a
* human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL error
+ * is thrown. Otherwise, 'fail_elevel' is used for the log message. If
+ * 'exitOnSigterm' is true and the command is killed by SIGTERM, we exit
+ * immediately.
*
- * This is currently used for recovery_end_command and archive_cleanup_command.
+ * Returns whether the command succeeded.
*/
-static void
+static bool
ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info,
- const char *lastRestartPointFileName)
+ bool failOnSignal, bool exitOnSigterm,
+ uint32 wait_event_info, int fail_elevel)
{
- char *xlogRecoveryCmd;
int rc;
Assert(command && commandName);
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
- lastRestartPointFileName);
-
ereport(DEBUG3,
(errmsg_internal("executing %s \"%s\"", commandName, command)));
@@ -153,18 +141,19 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
*/
fflush(NULL);
pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
+ rc = system(command);
pgstat_report_wait_end();
- pfree(xlogRecoveryCmd);
-
if (rc != 0)
{
+ if (exitOnSigterm && wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
/*
* If the failure was due to any sort of signal, it's best to punt and
* abort recovery. See comments in shell_restore().
*/
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : fail_elevel,
/*------
translator: First %s represents a postgresql.conf parameter name like
"recovery_end_command", the 2nd is the value of that parameter, the
@@ -172,4 +161,6 @@ ExecuteRecoveryCommand(const char *command, const char *commandName,
(errmsg("%s \"%s\": %s", commandName,
command, wait_result_to_str(rc))));
}
+
+ return (rc == 0);
}
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b5cb060d55..4b89addf97 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,7 +22,6 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
-#include "common/archive.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
diff --git a/src/common/Makefile b/src/common/Makefile
index 113029bf7b..2f424a5735 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -46,7 +46,6 @@ LIBS += $(PTHREAD_LIBS)
# If you add objects here, see also src/tools/msvc/Mkvcbuild.pm
OBJS_COMMON = \
- archive.o \
base64.o \
checksum_helper.o \
compression.o \
diff --git a/src/common/archive.c b/src/common/archive.c
deleted file mode 100644
index 641a58ee88..0000000000
--- a/src/common/archive.c
+++ /dev/null
@@ -1,60 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * archive.c
- * Common WAL archive routines
- *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- *
- * IDENTIFICATION
- * src/common/archive.c
- *
- *-------------------------------------------------------------------------
- */
-
-#ifndef FRONTEND
-#include "postgres.h"
-#else
-#include "postgres_fe.h"
-#endif
-
-#include "common/archive.h"
-#include "common/percentrepl.h"
-
-/*
- * BuildRestoreCommand
- *
- * Builds a restore command to retrieve a file from WAL archives, replacing
- * the supported aliases with values supplied by the caller as defined by
- * the GUC parameter restore_command: xlogpath for %p, xlogfname for %f and
- * lastRestartPointFname for %r.
- *
- * The result is a palloc'd string for the restore command built. The
- * caller is responsible for freeing it. If any of the required arguments
- * is NULL and that the corresponding alias is found in the command given
- * by the caller, then an error is thrown.
- */
-char *
-BuildRestoreCommand(const char *restoreCommand,
- const char *xlogpath,
- const char *xlogfname,
- const char *lastRestartPointFname)
-{
- char *nativePath = NULL;
- char *result;
-
- if (xlogpath)
- {
- nativePath = pstrdup(xlogpath);
- make_native_path(nativePath);
- }
-
- result = replace_percent_placeholders(restoreCommand, "restore_command", "frp",
- xlogfname, lastRestartPointFname, nativePath);
-
- if (nativePath)
- pfree(nativePath);
-
- return result;
-}
diff --git a/src/common/meson.build b/src/common/meson.build
index 41bd58ebdf..1caa1fed04 100644
--- a/src/common/meson.build
+++ b/src/common/meson.build
@@ -1,7 +1,6 @@
# Copyright (c) 2022-2023, PostgreSQL Global Development Group
common_sources = files(
- 'archive.c',
'base64.c',
'checksum_helper.c',
'compression.c',
diff --git a/src/common/percentrepl.c b/src/common/percentrepl.c
index d78571fec0..c083fd9b89 100644
--- a/src/common/percentrepl.c
+++ b/src/common/percentrepl.c
@@ -38,11 +38,6 @@
* This throws an error for an unsupported placeholder or a "%" at the end of
* the input string.
*
- * A value may be NULL. If the corresponding placeholder is found in the
- * input string, it will be treated as if an unsupported placeholder was used.
- * This allows callers to share a "letters" specification but vary the
- * actually supported placeholders at run time.
- *
* This functions is meant for cases where all the values are readily
* available or cheap to compute and most invocations will use most values
* (for example for archive_command). Also, it requires that all values are
@@ -101,12 +96,8 @@ replace_percent_placeholders(const char *instr, const char *param_name, const ch
if (*sp == *lp)
{
- if (val)
- {
- appendStringInfoString(&result, val);
- found = true;
- }
- /* If val is NULL, we will report an error. */
+ appendStringInfoString(&result, val);
+ found = true;
break;
}
}
diff --git a/src/fe_utils/archive.c b/src/fe_utils/archive.c
index eb1c930ae7..c1ce250c90 100644
--- a/src/fe_utils/archive.c
+++ b/src/fe_utils/archive.c
@@ -19,8 +19,8 @@
#include <sys/stat.h>
#include "access/xlog_internal.h"
-#include "common/archive.h"
#include "common/logging.h"
+#include "common/percentrepl.h"
#include "fe_utils/archive.h"
@@ -41,13 +41,18 @@ RestoreArchivedFile(const char *path, const char *xlogfname,
{
char xlogpath[MAXPGPATH];
char *xlogRestoreCmd;
+ char *nativePath;
int rc;
struct stat stat_buf;
snprintf(xlogpath, MAXPGPATH, "%s/" XLOGDIR "/%s", path, xlogfname);
- xlogRestoreCmd = BuildRestoreCommand(restoreCommand, xlogpath,
- xlogfname, NULL);
+ nativePath = pstrdup(xlogpath);
+ make_native_path(nativePath);
+ xlogRestoreCmd = replace_percent_placeholders(restoreCommand,
+ "restore_command", "fp",
+ xlogfname, nativePath);
+ pfree(nativePath);
/*
* Execute restore_command, which should copy the missing file from
diff --git a/src/include/common/archive.h b/src/include/common/archive.h
deleted file mode 100644
index 95196772c9..0000000000
--- a/src/include/common/archive.h
+++ /dev/null
@@ -1,21 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * archive.h
- * Common WAL archive routines
- *
- * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/common/archive.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef ARCHIVE_H
-#define ARCHIVE_H
-
-extern char *BuildRestoreCommand(const char *restoreCommand,
- const char *xlogpath, /* %p */
- const char *xlogfname, /* %f */
- const char *lastRestartPointFname); /* %r */
-
-#endif /* ARCHIVE_H */
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index f1c9ddf4a0..ee49424d6f 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -133,7 +133,7 @@ sub mkvcbuild
}
our @pgcommonallfiles = qw(
- archive.c base64.c checksum_helper.c compression.c
+ base64.c checksum_helper.c compression.c
config_info.c controldata_utils.c d2s.c encnames.c exec.c
f2s.c file_perm.c file_utils.c hashfn.c ip.c jsonapi.c
keywords.c kwlookup.c link-canary.c md5_common.c percentrepl.c
--
2.25.1
v8-0002-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From 145f533fffbf8c36eea4d0a45ff558b23339f3b1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v8 2/2] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 28cbb6cce0..8c333c8f99 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, unconstify(char *, path));
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..f44135061d 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 60f23d2855..11fd670dbc 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 77574e2d4e..039d3360ac 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 4752be0b9f..90283c601b 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -4,7 +4,8 @@
* Recovery functions for a user-specified shell command.
*
* These recovery functions use a user-specified shell command (e.g. based
- * on the GUC restore_command).
+ * on the GUC restore_command). It is used as the default, but other
+ * modules may define their own recovery logic.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -24,11 +25,25 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal, bool exitOnSigterm,
uint32 wait_event_info, int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
/*
* Attempt to execute a shell-based restore command.
*
@@ -83,7 +98,7 @@ shell_restore(const char *file, const char *path,
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -99,7 +114,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8f47fb7570..ae537cd87f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4884,15 +4884,16 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7307,14 +7308,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b89addf97..4af5689c25 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,6 +22,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -31,6 +33,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -70,7 +77,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -148,14 +155,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -602,3 +610,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 5e65785306..db0cd4469a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..a8a516b0c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..38fb3e0823 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Tue, Jan 17, 2023 at 10:23:56AM -0800, Nathan Bossart wrote:
Yeah, this seems cleaner. I removed BuildRestoreCommand() in v8.
if (*sp == *lp)
{
- if (val)
- {
- appendStringInfoString(&result, val);
- found = true;
- }
- /* If val is NULL, we will report an error. */
+ appendStringInfoString(&result, val);
+ found = true;
In 0002, this code block has been removed as an effect of the removal
of BuildRestoreCommand(), because RestoreArchivedFile() needs to
handle two flags with two values. The current design has the
advantage to warn extension developers with an unexpected
manipulation, as well, so I have kept the logic in percentrepl.c
as-is.
I was wondering also if ExecuteRecoveryCommand() should use a bits32
for its two boolean flags, but did not bother as it is static in
shell_restore.c so ABI does not matter, even if there are three
callers of it with 75% of the combinations possible (only false/true
is not used).
And 0002 is applied.
--
Michael
On Wed, Jan 18, 2023 at 11:29:20AM +0900, Michael Paquier wrote:
And 0002 is applied.
Thanks. Here's a rebased version of the last patch.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v9-0001-Allow-recovery-via-loadable-modules.patchtext/x-diff; charset=us-asciiDownload
From c262ed4701fdb417c1af3a6730499cebb9e25030 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 9 Dec 2022 19:40:54 -0800
Subject: [PATCH v9 1/1] Allow recovery via loadable modules.
This adds the restore_library parameter to allow archive recovery
via a loadable module, rather than running shell commands.
---
contrib/basic_archive/Makefile | 4 +-
contrib/basic_archive/basic_archive.c | 67 ++++++-
contrib/basic_archive/meson.build | 7 +-
contrib/basic_archive/t/001_restore.pl | 44 +++++
doc/src/sgml/archive-modules.sgml | 168 ++++++++++++++++--
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-archive.sgml | 33 ++--
doc/src/sgml/config.sgml | 54 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 +++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/pgarch.c | 7 +-
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc.c | 14 ++
src/backend/utils/misc/guc_tables.c | 10 ++
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/include/utils/guc.h | 2 +
23 files changed, 618 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..487dc563f3 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -1,7 +1,7 @@
# contrib/basic_archive/Makefile
MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+PGFILEDESC = "basic_archive - basic archive and recovery module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..225a9bd3d4 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_archive.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive", file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..af4580dea9 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -7,7 +7,7 @@ basic_archive_sources = files(
if host_system == 'windows'
basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
'--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
+ '--FILEDESC', 'basic_archive - basic archive and recovery module',])
endif
basic_archive = shared_module('basic_archive',
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..ec8767d740
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..53e657040b 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -1,34 +1,40 @@
<!-- doc/src/sgml/archive-modules.sgml -->
<chapter id="archive-modules">
- <title>Archive Modules</title>
+ <title>Archive and Recovery Modules</title>
<indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+ <primary>Archive and Recovery Modules</primary>
</indexterm>
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Archive and recovery modules must at least consist of an initialization
+ function (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, archive and recovery
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -37,7 +43,7 @@
</para>
<sect1 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -64,6 +70,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -129,6 +141,132 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with the
+ <xref linkend="guc-restore-library"/> as the library base name. The normal
+ library search path is used to locate the library. To provide the required
+ recovery module callbacks and to indicate that the library is actually a
+ recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect1>
+
+ <sect1 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The server
+ will call them as required to execute recovery actions.
+ </para>
+
+ <sect2 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a single
+ archived segment of the WAL file series for archive recovery or streaming
+ replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable> contains
+ the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the end
+ of recovery and is intended to provide a mechanism for cleanup following
+ replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect2>
+
+ <sect2 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process that
+ has loaded the recovery module exits (e.g., after an error) or the value of
+ <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect2>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..f44135061d 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index 60f23d2855..11fd670dbc 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and recovery
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about archive and recovery modules, see
+ see <xref linkend="archive-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_archive</filename> is used as an archive module, server
+ crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89d53f2a64..ecdbfd71f7 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3773,7 +3773,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3801,7 +3802,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3836,7 +3838,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="archive-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3881,7 +3918,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3910,11 +3950,13 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
-
</variablelist>
</sect2>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 8458209f49..dfd409905c 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -4,7 +4,8 @@
* Recovery functions for a user-specified shell command.
*
* These recovery functions use a user-specified shell command (e.g. based
- * on the GUC restore_command).
+ * on the GUC restore_command). It is used as the default, but other
+ * modules may define their own recovery logic.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -24,6 +25,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
@@ -31,6 +36,16 @@ static bool ExecuteRecoveryCommand(const char *command,
uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
/*
* Attempt to execute a shell-based restore command.
*
@@ -88,7 +103,7 @@ shell_restore(const char *file, const char *path,
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -104,7 +119,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8f47fb7570..ae537cd87f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4884,15 +4884,16 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7307,14 +7308,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b89addf97..4af5689c25 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,6 +22,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -31,6 +33,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -70,7 +77,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -148,14 +155,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -602,3 +610,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 5e65785306..db0cd4469a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 5025e80f89..a8a516b0c6 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3776,6 +3776,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 4cceda4162..38fb3e0823 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -269,6 +269,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
On Tue, Jan 17, 2023 at 08:44:27PM -0800, Nathan Bossart wrote:
Thanks. Here's a rebased version of the last patch.
Thanks for the rebase.
The final state of the documentation is as follows:
51. Archive and Recovery Modules
51.1. Archive Module Initialization Functions
51.2. Archive Module Callbacks
51.3. Recovery Module Initialization Functions
51.4. Recovery Module Callbacks
I am not completely sure whether this grouping is the best thing to
do. Wouldn't it be better to move that into two different
sub-sections instead? One layout suggestion:
51. WAL Modules
51.1. Archive Modules
51.1.1. Archive Module Initialization Functions
51.1.2. Archive Module Callbacks
51.2. Recovery Modules
51.2.1. Recovery Module Initialization Functions
51.2.2. Recovery Module Callbacks
Putting both of them in the same section sounds like a good idea per
the symmetry that one would likely have between the code paths of
archiving and recovery, so as they share some common knowledge.
This kinds of comes back to the previous suggestion to rename
basic_archive to something like wal_modules, that covers both
archiving and recovery. I does not seem like this would overlap with
RMGRs, which is much more internal anyway.
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
This is long. Shouldn't this be split into an errdetail() to list all
the options at hand?
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
The introduction of this routine could be a patch on its own, as it
impacts the archiving path.
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
Hm.. The callers of CheckMutuallyExclusiveGUCs() with the new ERROR
paths they introduce need a close lookup. As far as I can see this
concerns four areas depending on the three restore commands
(restore_command and recovery_end command for startup,
archive_cleanup_command for the checkpointer):
- Startup process initialization, as of validateRecoveryParameters()
where the postmaster GUCs for the recovery target are evaluated. This
one is an early stage which is fine.
- Startup reloading, as of StartupRereadConfig(). This code could
involve a WAL receiver restart depending on a change in the slot
change or in primary_conninfo, and force at the same time an ERROR
because of conflicting recovery library and command configuration.
This one should be safe because pendingWalRcvRestart would just be
considered later on by the startup process itself while waiting for
WAL to become available. Still this could deserve a comment? Even if
there is a misconfiguration, a reload on a standby would enforce a
FATAL in the startup process, taking down the whole server.
- Checkpointer initialization, as of CheckpointerMain(). A
configuration failure in this code path, aka server startup, causes
the server to loop infinitely on FATAL with the misconfiguration
showing up all the time.. This is a problem.
- Last comes the checkpointer GUC reloading, as of
HandleCheckpointerInterrupts(), with a second problem. This
introduces a failure path where ConfigReloadPending is processed at
the same time as ShutdownRequestPending based on the way it is coded,
interacting with what would be a normal shutdown in some cases? And
actually, if you enforce a misconfiguration on reload, the
checkpointer reports an error but it does not enforce a process
restart, hence it keeps around the new, incorrect, configuration while
waiting for a new checkpoint to happen once restore_library and
archive_cleanup_command are set. This could lead to surprises, IMO.
Upgrading to a FATAL in this code path triggers an infinite loop, like
the startup path.
If the archive_cleanup_command ballback of a restore library triggers
a FATAL, it seems to me that it would continuously trigger a server
restart, actually. Perhaps that's something to document, in
comparison to the safe fallbacks of the shell command where we don't
force an ERROR to give priority to the stability of the checkpointer.
--
Michael
On Mon, Jan 23, 2023 at 11:44:57AM +0900, Michael Paquier wrote:
Thanks for the rebase.
Thanks for the detailed review.
The final state of the documentation is as follows:
51. Archive and Recovery Modules
51.1. Archive Module Initialization Functions
51.2. Archive Module Callbacks
51.3. Recovery Module Initialization Functions
51.4. Recovery Module CallbacksI am not completely sure whether this grouping is the best thing to
do. Wouldn't it be better to move that into two different
sub-sections instead? One layout suggestion:
51. WAL Modules
51.1. Archive Modules
51.1.1. Archive Module Initialization Functions
51.1.2. Archive Module Callbacks
51.2. Recovery Modules
51.2.1. Recovery Module Initialization Functions
51.2.2. Recovery Module CallbacksPutting both of them in the same section sounds like a good idea per
the symmetry that one would likely have between the code paths of
archiving and recovery, so as they share some common knowledge.This kinds of comes back to the previous suggestion to rename
basic_archive to something like wal_modules, that covers both
archiving and recovery. I does not seem like this would overlap with
RMGRs, which is much more internal anyway.
I updated the documentation as you suggested, and I renamed basic_archive
to basic_wal_module.
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("must specify restore_command when standby mode is not enabled"))); + errmsg("must specify restore_command or a restore_library that defines " + "a restore callback when standby mode is not enabled"))); This is long. Shouldn't this be split into an errdetail() to list all the options at hand?
Should the errmsg() be something like "recovery parameters misconfigured"?
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("both archive_command and archive_library set"), - errdetail("Only one of archive_command, archive_library may be set."))); + CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library", + XLogArchiveCommand, "archive_command");The introduction of this routine could be a patch on its own, as it
impacts the archiving path.
I moved this to a separate patch.
- Startup reloading, as of StartupRereadConfig(). This code could
involve a WAL receiver restart depending on a change in the slot
change or in primary_conninfo, and force at the same time an ERROR
because of conflicting recovery library and command configuration.
This one should be safe because pendingWalRcvRestart would just be
considered later on by the startup process itself while waiting for
WAL to become available. Still this could deserve a comment? Even if
there is a misconfiguration, a reload on a standby would enforce a
FATAL in the startup process, taking down the whole server.
Do you think the parameter checks should go before the WAL receiver restart
logic?
- Checkpointer initialization, as of CheckpointerMain(). A
configuration failure in this code path, aka server startup, causes
the server to loop infinitely on FATAL with the misconfiguration
showing up all the time.. This is a problem.
Perhaps this is a reason to move the parameter check in CheckpointerMain()
to after the sigsetjmp() block. This should avoid full server restarts.
Only the checkpointer process would loop with the ERROR.
- Last comes the checkpointer GUC reloading, as of
HandleCheckpointerInterrupts(), with a second problem. This
introduces a failure path where ConfigReloadPending is processed at
the same time as ShutdownRequestPending based on the way it is coded,
interacting with what would be a normal shutdown in some cases? And
actually, if you enforce a misconfiguration on reload, the
checkpointer reports an error but it does not enforce a process
restart, hence it keeps around the new, incorrect, configuration while
waiting for a new checkpoint to happen once restore_library and
archive_cleanup_command are set. This could lead to surprises, IMO.
Upgrading to a FATAL in this code path triggers an infinite loop, like
the startup path.
If we move the parameter check in CheckpointerMain() as described above,
the checkpointer should be unable to proceed with an incorrect
configuration. For the normal shutdown part, do you think the
ShutdownRequestPending block should be moved to before the
ConfigReloadPending block in HandleCheckpointerInterrupts()?
If the archive_cleanup_command ballback of a restore library triggers
a FATAL, it seems to me that it would continuously trigger a server
restart, actually. Perhaps that's something to document, in
comparison to the safe fallbacks of the shell command where we don't
force an ERROR to give priority to the stability of the checkpointer.
I'm not sure it's worth documenting that ereport(FATAL, ...) in the
checkpointer process will cause a server restart. In most cases, an
extension author would use ERROR, which, if we make the aforementioned
changes, would at most cause the checkpointer to effectively restart. This
is similar to archive modules where an ERROR causes only the archiver
process to restart. Also, we document that recovery libraries are loaded
in the startup and checkpointer processes, so IMO it should be relatively
apparent that doing something like FATAL or proc_exit() is bad.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v10-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From a183646e0509d07ebfefbb3b3460eca8de6d7516 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Mon, 23 Jan 2023 09:49:22 -0800
Subject: [PATCH v10 1/4] introduce routine for checking mutually exclusive
parameters
---
src/backend/postmaster/pgarch.c | 7 ++-----
src/backend/utils/misc/guc.c | 14 ++++++++++++++
src/include/utils/guc.h | 2 ++
3 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..7858e9a649 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -6880,3 +6880,17 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
return true;
}
+
+/*
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both %s and %s set", p1name, p2name),
+ errdetail("Only one of %s, %s may be set.", p1name, p2name)));
+}
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..947597247f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -404,6 +404,8 @@ extern void *guc_malloc(int elevel, size_t size);
extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
extern char *guc_strdup(int elevel, const char *src);
extern void guc_free(void *ptr);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
#ifdef EXEC_BACKEND
extern void write_nondefault_variables(GucContext context);
--
2.25.1
v10-0002-create-WAL-modules-chapter-in-docs-in-preparatio.patchtext/x-diff; charset=us-asciiDownload
From 40a11f8958f0d908ec7eb258b6ae4cb5b8a12465 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Mon, 23 Jan 2023 10:35:52 -0800
Subject: [PATCH v10 2/4] create WAL modules chapter in docs in preparation for
recovery modules
---
doc/src/sgml/archive-modules.sgml | 136 ----------------------------
doc/src/sgml/backup.sgml | 2 +-
doc/src/sgml/basic-archive.sgml | 2 +-
doc/src/sgml/config.sgml | 2 +-
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
doc/src/sgml/system-views.sgml | 2 +-
doc/src/sgml/wal-modules.sgml | 144 ++++++++++++++++++++++++++++++
8 files changed, 150 insertions(+), 142 deletions(-)
delete mode 100644 doc/src/sgml/archive-modules.sgml
create mode 100644 doc/src/sgml/wal-modules.sgml
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
deleted file mode 100644
index ef02051f7f..0000000000
--- a/doc/src/sgml/archive-modules.sgml
+++ /dev/null
@@ -1,136 +0,0 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
-
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
- </indexterm>
-
- <para>
- PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
- </para>
-
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
- <para>
- The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
- </para>
-
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
- </indexterm>
- <para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
-
-<programlisting>
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
-</programlisting>
-
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
-
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
-
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
-
-<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-</programlisting>
-
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
-<screen>
-WARNING: archive_mode enabled, yet archiving is not configured
-</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
-
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
-
-<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-</programlisting>
-
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
-
-<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
-</programlisting>
- </para>
- </sect2>
- </sect1>
-</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..12d9bb3f81 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -660,7 +660,7 @@ test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/0
than writing a shell command. However, archive modules can be more
performant than archiving via shell, and they will have access to many
useful server resources. For more information about archive modules, see
- <xref linkend="archive-modules"/>.
+ <xref linkend="wal-modules"/>.
</para>
<para>
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..a877564af2 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -12,7 +12,7 @@
module copies completed WAL segment files to the specified directory. This
may not be especially useful, but it can serve as a starting point for
developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ modules, see <xref linkend="wal-modules"/>.
</para>
<para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f985afc009..bba24bdcb8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3666,7 +3666,7 @@ include_dir 'conf.d'
shared library is used for archiving. The WAL archiver process is
restarted by the postmaster when this parameter changes. For more
information, see <xref linkend="backup-archiving-wal"/> and
- <xref linkend="archive-modules"/>.
+ <xref linkend="wal-modules"/>.
</para>
<para>
This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 0d6be9a2fa..f7b6d8b1be 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY wal-modules SYSTEM "wal-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..817e774155 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &wal-modules;
</part>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 7c8fc3f654..e93e733051 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -3351,7 +3351,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<xref linkend="guc-shared-preload-libraries"/>,
a call to a C function in the extension, or the
<link linkend="sql-load"><command>LOAD</command></link> command).
- For example, since <link linkend="archive-modules">archive modules</link>
+ For example, since <link linkend="wal-modules">archive modules</link>
are normally loaded only by the archiver process not regular sessions,
this view will not display any customized options defined by such modules
unless special action is taken to load them into the backend process
diff --git a/doc/src/sgml/wal-modules.sgml b/doc/src/sgml/wal-modules.sgml
new file mode 100644
index 0000000000..6fd2763e41
--- /dev/null
+++ b/doc/src/sgml/wal-modules.sgml
@@ -0,0 +1,144 @@
+<!-- doc/src/sgml/wal-modules.sgml -->
+
+<chapter id="wal-modules">
+ <title>Write-Ahead Log Modules</title>
+ <indexterm zone="wal-modules">
+ <primary>Write-Ahead Log Modules</primary>
+ </indexterm>
+
+ <para>
+ PostgreSQL provides infrastructure to create custom modules for continuous
+ archiving (see <xref linkend="continuous-archiving"/>). While archiving via
+ a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
+ simpler, a custom WAL module will often be considerably more robust and
+ performant.
+ </para>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
+ will submit completed WAL files to the module, and the server will avoid
+ recycling or removing these WAL files until the module indicates that the
+ files were successfully archived. It is ultimately up to the module to
+ decide what to do with each WAL file, but many recommendations are listed at
+ <xref linkend="backup-archiving-wal"/>.
+ </para>
+
+ <para>
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <para>
+ The <filename>contrib/basic_archive</filename> module contains a working
+ example, which demonstrates some useful techniques.
+ </para>
+
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
+ </indexterm>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+</programlisting>
+
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
+
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
+ <para>
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
+
+<programlisting>
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
+<screen>
+WARNING: archive_mode enabled, yet archiving is not configured
+</screen>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
+
+<programlisting>
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
+</chapter>
--
2.25.1
v10-0003-rename-basic_archive-to-basic_wal_module.patchtext/x-diff; charset=us-asciiDownload
From 26060d468550df673987d971aaeebf4ea8e4daff Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Mon, 23 Jan 2023 11:21:36 -0800
Subject: [PATCH v10 3/4] rename basic_archive to basic_wal_module
---
contrib/Makefile | 2 +-
contrib/basic_archive/basic_archive.conf | 4 ---
contrib/basic_archive/meson.build | 34 -------------------
.../.gitignore | 0
.../Makefile | 14 ++++----
.../basic_wal_module.c} | 26 +++++++-------
.../basic_wal_module/basic_wal_module.conf | 4 +++
.../expected/basic_wal_module.out} | 0
contrib/basic_wal_module/meson.build | 34 +++++++++++++++++++
.../sql/basic_wal_module.sql} | 0
contrib/meson.build | 2 +-
.../sgml/appendix-obsolete-basic-archive.sgml | 25 ++++++++++++++
doc/src/sgml/appendix-obsolete.sgml | 1 +
...sic-archive.sgml => basic-wal-module.sgml} | 30 ++++++++--------
doc/src/sgml/contrib.sgml | 2 +-
doc/src/sgml/filelist.sgml | 3 +-
doc/src/sgml/wal-modules.sgml | 2 +-
17 files changed, 105 insertions(+), 78 deletions(-)
delete mode 100644 contrib/basic_archive/basic_archive.conf
delete mode 100644 contrib/basic_archive/meson.build
rename contrib/{basic_archive => basic_wal_module}/.gitignore (100%)
rename contrib/{basic_archive => basic_wal_module}/Makefile (55%)
rename contrib/{basic_archive/basic_archive.c => basic_wal_module/basic_wal_module.c} (93%)
create mode 100644 contrib/basic_wal_module/basic_wal_module.conf
rename contrib/{basic_archive/expected/basic_archive.out => basic_wal_module/expected/basic_wal_module.out} (100%)
create mode 100644 contrib/basic_wal_module/meson.build
rename contrib/{basic_archive/sql/basic_archive.sql => basic_wal_module/sql/basic_wal_module.sql} (100%)
create mode 100644 doc/src/sgml/appendix-obsolete-basic-archive.sgml
rename doc/src/sgml/{basic-archive.sgml => basic-wal-module.sgml} (64%)
diff --git a/contrib/Makefile b/contrib/Makefile
index bbf220407b..98acaf8690 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -9,7 +9,7 @@ SUBDIRS = \
amcheck \
auth_delay \
auto_explain \
- basic_archive \
+ basic_wal_module \
basebackup_to_shell \
bloom \
btree_gin \
diff --git a/contrib/basic_archive/basic_archive.conf b/contrib/basic_archive/basic_archive.conf
deleted file mode 100644
index 7c82a4b82b..0000000000
--- a/contrib/basic_archive/basic_archive.conf
+++ /dev/null
@@ -1,4 +0,0 @@
-archive_mode = on
-archive_library = 'basic_archive'
-basic_archive.archive_directory = '.'
-wal_level = replica
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
deleted file mode 100644
index bc1380e6f6..0000000000
--- a/contrib/basic_archive/meson.build
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (c) 2022-2023, PostgreSQL Global Development Group
-
-basic_archive_sources = files(
- 'basic_archive.c',
-)
-
-if host_system == 'windows'
- basic_archive_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
- '--NAME', 'basic_archive',
- '--FILEDESC', 'basic_archive - basic archive module',])
-endif
-
-basic_archive = shared_module('basic_archive',
- basic_archive_sources,
- kwargs: contrib_mod_args,
-)
-contrib_targets += basic_archive
-
-tests += {
- 'name': 'basic_archive',
- 'sd': meson.current_source_dir(),
- 'bd': meson.current_build_dir(),
- 'regress': {
- 'sql': [
- 'basic_archive',
- ],
- 'regress_args': [
- '--temp-config', files('basic_archive.conf'),
- ],
- # Disabled because these tests require "shared_preload_libraries=basic_archive",
- # which typical runningcheck users do not have (e.g. buildfarm clients).
- 'runningcheck': false,
- },
-}
diff --git a/contrib/basic_archive/.gitignore b/contrib/basic_wal_module/.gitignore
similarity index 100%
rename from contrib/basic_archive/.gitignore
rename to contrib/basic_wal_module/.gitignore
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_wal_module/Makefile
similarity index 55%
rename from contrib/basic_archive/Makefile
rename to contrib/basic_wal_module/Makefile
index 55d299d650..1f88aaf469 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_wal_module/Makefile
@@ -1,11 +1,11 @@
-# contrib/basic_archive/Makefile
+# contrib/basic_wal_module/Makefile
-MODULES = basic_archive
-PGFILEDESC = "basic_archive - basic archive module"
+MODULES = basic_wal_module
+PGFILEDESC = "basic_wal_module - basic write-ahead log module"
-REGRESS = basic_archive
-REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
-# Disabled because these tests require "shared_preload_libraries=basic_archive",
+REGRESS = basic_wal_module
+REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_wal_module/basic_wal_module.conf
+# Disabled because these tests require "shared_preload_libraries=basic_wal_module",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
@@ -14,7 +14,7 @@ PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
else
-subdir = contrib/basic_archive
+subdir = contrib/basic_wal_module
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
include $(top_srcdir)/contrib/contrib-global.mk
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_wal_module/basic_wal_module.c
similarity index 93%
rename from contrib/basic_archive/basic_archive.c
rename to contrib/basic_wal_module/basic_wal_module.c
index 3d29711a31..78c36656a8 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_wal_module/basic_wal_module.c
@@ -1,6 +1,6 @@
/*-------------------------------------------------------------------------
*
- * basic_archive.c
+ * basic_wal_module.c
*
* This file demonstrates a basic archive library implementation that is
* roughly equivalent to the following shell command:
@@ -20,7 +20,7 @@
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * contrib/basic_archive/basic_archive.c
+ * contrib/basic_wal_module/basic_wal_module.c
*
*-------------------------------------------------------------------------
*/
@@ -41,7 +41,7 @@
PG_MODULE_MAGIC;
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
+static MemoryContext basic_wal_module_context;
static bool basic_archive_configured(void);
static bool basic_archive_file(const char *file, const char *path);
@@ -57,7 +57,7 @@ static bool compare_files(const char *file1, const char *file2);
void
_PG_init(void)
{
- DefineCustomStringVariable("basic_archive.archive_directory",
+ DefineCustomStringVariable("basic_wal_module.archive_directory",
gettext_noop("Archive file destination directory."),
NULL,
&archive_directory,
@@ -66,11 +66,11 @@ _PG_init(void)
0,
check_archive_directory, NULL, NULL);
- MarkGUCPrefixReserved("basic_archive");
+ MarkGUCPrefixReserved("basic_wal_module");
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
+ basic_wal_module_context = AllocSetContextCreate(TopMemoryContext,
+ "basic_wal_module",
+ ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -156,7 +156,7 @@ basic_archive_file(const char *file, const char *path)
* we can easily reset it during error recovery (thus avoiding memory
* leaks).
*/
- oldcontext = MemoryContextSwitchTo(basic_archive_context);
+ oldcontext = MemoryContextSwitchTo(basic_wal_module_context);
/*
* Since the archiver operates at the bottom of the exception stack,
@@ -183,7 +183,7 @@ basic_archive_file(const char *file, const char *path)
/* Reset our memory context and switch back to the original one */
MemoryContextSwitchTo(oldcontext);
- MemoryContextReset(basic_archive_context);
+ MemoryContextReset(basic_wal_module_context);
/* Remove our exception handler */
PG_exception_stack = NULL;
@@ -206,7 +206,7 @@ basic_archive_file(const char *file, const char *path)
/* Reset our memory context and switch back to the original one */
MemoryContextSwitchTo(oldcontext);
- MemoryContextReset(basic_archive_context);
+ MemoryContextReset(basic_wal_module_context);
return true;
}
@@ -221,7 +221,7 @@ basic_archive_file_internal(const char *file, const char *path)
uint64 epoch; /* milliseconds */
ereport(DEBUG3,
- (errmsg("archiving \"%s\" via basic_archive", file)));
+ (errmsg("archiving \"%s\" via basic_wal_module", file)));
snprintf(destination, MAXPGPATH, "%s/%s", archive_directory, file);
@@ -285,7 +285,7 @@ basic_archive_file_internal(const char *file, const char *path)
(void) durable_rename(temp, destination, ERROR);
ereport(DEBUG1,
- (errmsg("archived \"%s\" via basic_archive", file)));
+ (errmsg("archived \"%s\" via basic_wal_module", file)));
}
/*
diff --git a/contrib/basic_wal_module/basic_wal_module.conf b/contrib/basic_wal_module/basic_wal_module.conf
new file mode 100644
index 0000000000..9a4ffacf68
--- /dev/null
+++ b/contrib/basic_wal_module/basic_wal_module.conf
@@ -0,0 +1,4 @@
+archive_mode = on
+archive_library = 'basic_wal_module'
+basic_wal_module.archive_directory = '.'
+wal_level = replica
diff --git a/contrib/basic_archive/expected/basic_archive.out b/contrib/basic_wal_module/expected/basic_wal_module.out
similarity index 100%
rename from contrib/basic_archive/expected/basic_archive.out
rename to contrib/basic_wal_module/expected/basic_wal_module.out
diff --git a/contrib/basic_wal_module/meson.build b/contrib/basic_wal_module/meson.build
new file mode 100644
index 0000000000..59939d71c4
--- /dev/null
+++ b/contrib/basic_wal_module/meson.build
@@ -0,0 +1,34 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+basic_wal_module_sources = files(
+ 'basic_wal_module.c',
+)
+
+if host_system == 'windows'
+ basic_wal_module_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'basic_wal_module',
+ '--FILEDESC', 'basic_wal_module - basic write-ahead log module',])
+endif
+
+basic_wal_module = shared_module('basic_wal_module',
+ basic_wal_module_sources,
+ kwargs: contrib_mod_args,
+)
+contrib_targets += basic_wal_module
+
+tests += {
+ 'name': 'basic_wal_module',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'basic_wal_module',
+ ],
+ 'regress_args': [
+ '--temp-config', files('basic_wal_module.conf'),
+ ],
+ # Disabled because these tests require "shared_preload_libraries=basic_wal_module",
+ # which typical runningcheck users do not have (e.g. buildfarm clients).
+ 'runningcheck': false,
+ },
+}
diff --git a/contrib/basic_archive/sql/basic_archive.sql b/contrib/basic_wal_module/sql/basic_wal_module.sql
similarity index 100%
rename from contrib/basic_archive/sql/basic_archive.sql
rename to contrib/basic_wal_module/sql/basic_wal_module.sql
diff --git a/contrib/meson.build b/contrib/meson.build
index bd4a57c43c..2db77a18d7 100644
--- a/contrib/meson.build
+++ b/contrib/meson.build
@@ -11,7 +11,7 @@ subdir('adminpack')
subdir('amcheck')
subdir('auth_delay')
subdir('auto_explain')
-subdir('basic_archive')
+subdir('basic_wal_module')
subdir('bloom')
subdir('basebackup_to_shell')
subdir('bool_plperl')
diff --git a/doc/src/sgml/appendix-obsolete-basic-archive.sgml b/doc/src/sgml/appendix-obsolete-basic-archive.sgml
new file mode 100644
index 0000000000..5070b3b6ab
--- /dev/null
+++ b/doc/src/sgml/appendix-obsolete-basic-archive.sgml
@@ -0,0 +1,25 @@
+<!-- doc/src/sgml/appendix-obsolete-basic-archive.sgml -->
+<!--
+ See doc/src/sgml/appendix-obsolete.sgml for why this file exists. Do not change the id attribute.
+-->
+
+<sect1 id="basic-archive" xreflabel="basic_archive">
+ <title><command>basic_archive</command> renamed to <command>basic_wal_module</command></title>
+
+ <indexterm>
+ <primary>basic_archive</primary>
+ <see>basic_wal_module</see>
+ </indexterm>
+
+ <para>
+ PostgreSQL 15 provided an archive module named
+ <filename>basic_archive</filename>
+ <indexterm><primary>basic_archive</primary></indexterm>.
+ This module was renamed to <filename>basic_wal_module</filename>. See
+ <xref linkend="basic-wal-module"/> for documentation of
+ <filename>basic_wal_module</filename>, and see
+ <link linkend="release-prior">the release notes for PostgreSQL 16</link>
+ for details on this change.
+ </para>
+
+</sect1>
diff --git a/doc/src/sgml/appendix-obsolete.sgml b/doc/src/sgml/appendix-obsolete.sgml
index b1a00c8ce6..87c1b1020d 100644
--- a/doc/src/sgml/appendix-obsolete.sgml
+++ b/doc/src/sgml/appendix-obsolete.sgml
@@ -38,5 +38,6 @@
&obsolete-pgxlogdump;
&obsolete-pgresetxlog;
&obsolete-pgreceivexlog;
+ &obsolete-basic-archive;
</appendix>
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-wal-module.sgml
similarity index 64%
rename from doc/src/sgml/basic-archive.sgml
rename to doc/src/sgml/basic-wal-module.sgml
index a877564af2..f972566374 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-wal-module.sgml
@@ -1,16 +1,16 @@
-<!-- doc/src/sgml/basic-archive.sgml -->
+<!-- doc/src/sgml/basic-wal-module.sgml -->
-<sect1 id="basic-archive" xreflabel="basic_archive">
- <title>basic_archive — an example WAL archive module</title>
+<sect1 id="basic-wal-module" xreflabel="basic_wal_module">
+ <title>basic_wal_module — an example write-ahead log module</title>
- <indexterm zone="basic-archive">
- <primary>basic_archive</primary>
+ <indexterm zone="basic-wal-module">
+ <primary>basic_wal_module</primary>
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
+ <filename>basic_wal_module</filename> is an example of an archive module.
+ This module copies completed WAL segment files to the specified directory.
+ This may not be especially useful, but it can serve as a starting point for
developing your own archive module. For more information about archive
modules, see <xref linkend="wal-modules"/>.
</para>
@@ -21,15 +21,15 @@
must be enabled.
</para>
- <sect2 id="basic-archive-configuration-parameters">
+ <sect2 id="basic-wal-module-configuration-parameters">
<title>Configuration Parameters</title>
<variablelist>
<varlistentry>
<term>
- <varname>basic_archive.archive_directory</varname> (<type>string</type>)
+ <varname>basic_wal_module.archive_directory</varname> (<type>string</type>)
<indexterm>
- <primary><varname>basic_archive.archive_directory</varname> configuration parameter</primary>
+ <primary><varname>basic_wal_module.archive_directory</varname> configuration parameter</primary>
</indexterm>
</term>
<listitem>
@@ -52,12 +52,12 @@
<programlisting>
# postgresql.conf
archive_mode = 'on'
-archive_library = 'basic_archive'
-basic_archive.archive_directory = '/path/to/archive/directory'
+archive_library = 'basic_wal_module'
+basic_wal_module.archive_directory = '/path/to/archive/directory'
</programlisting>
</sect2>
- <sect2 id="basic-archive-notes">
+ <sect2 id="basic-wal-module-notes">
<title>Notes</title>
<para>
@@ -70,7 +70,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
</para>
</sect2>
- <sect2 id="basic-archive-author">
+ <sect2 id="basic-wal-module-author">
<title>Author</title>
<para>
diff --git a/doc/src/sgml/contrib.sgml b/doc/src/sgml/contrib.sgml
index 12c79b798b..3225284eea 100644
--- a/doc/src/sgml/contrib.sgml
+++ b/doc/src/sgml/contrib.sgml
@@ -105,7 +105,7 @@ CREATE EXTENSION <replaceable>extension_name</replaceable>;
&auth-delay;
&auto-explain;
&basebackup-to-shell;
- &basic-archive;
+ &basic-wal-module;
&bloom;
&btree-gin;
&btree-gist;
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index f7b6d8b1be..31b4dc8fba 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -116,7 +116,7 @@
<!ENTITY amcheck SYSTEM "amcheck.sgml">
<!ENTITY auth-delay SYSTEM "auth-delay.sgml">
<!ENTITY auto-explain SYSTEM "auto-explain.sgml">
-<!ENTITY basic-archive SYSTEM "basic-archive.sgml">
+<!ENTITY basic-wal-module SYSTEM "basic-wal-module.sgml">
<!ENTITY basebackup-to-shell SYSTEM "basebackup-to-shell.sgml">
<!ENTITY bloom SYSTEM "bloom.sgml">
<!ENTITY btree-gin SYSTEM "btree-gin.sgml">
@@ -200,3 +200,4 @@
<!ENTITY obsolete-pgxlogdump SYSTEM "appendix-obsolete-pgxlogdump.sgml">
<!ENTITY obsolete-pgresetxlog SYSTEM "appendix-obsolete-pgresetxlog.sgml">
<!ENTITY obsolete-pgreceivexlog SYSTEM "appendix-obsolete-pgreceivexlog.sgml">
+<!ENTITY obsolete-basic-archive SYSTEM "appendix-obsolete-basic-archive.sgml">
diff --git a/doc/src/sgml/wal-modules.sgml b/doc/src/sgml/wal-modules.sgml
index 6fd2763e41..451c591e17 100644
--- a/doc/src/sgml/wal-modules.sgml
+++ b/doc/src/sgml/wal-modules.sgml
@@ -32,7 +32,7 @@
</para>
<para>
- The <filename>contrib/basic_archive</filename> module contains a working
+ The <filename>contrib/basic_wal_module</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
--
2.25.1
v10-0004-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 36a2e15c109eeeb99d02c6aaad3cbc58b2064908 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Mon, 23 Jan 2023 11:47:58 -0800
Subject: [PATCH v10 4/4] introduce restore_library
---
contrib/basic_wal_module/Makefile | 2 +
contrib/basic_wal_module/basic_wal_module.c | 69 ++++++-
contrib/basic_wal_module/meson.build | 5 +
contrib/basic_wal_module/t/001_restore.pl | 44 +++++
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-wal-module.sgml | 33 ++--
doc/src/sgml/config.sgml | 53 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
doc/src/sgml/wal-modules.sgml | 171 ++++++++++++++++--
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 13 +-
src/backend/access/transam/xlogarchive.c | 70 ++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc_tables.c | 10 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
20 files changed, 604 insertions(+), 75 deletions(-)
create mode 100644 contrib/basic_wal_module/t/001_restore.pl
diff --git a/contrib/basic_wal_module/Makefile b/contrib/basic_wal_module/Makefile
index 1f88aaf469..b92126ff52 100644
--- a/contrib/basic_wal_module/Makefile
+++ b/contrib/basic_wal_module/Makefile
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_wal_module/basic_wal_mo
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_wal_module/basic_wal_module.c b/contrib/basic_wal_module/basic_wal_module.c
index 78c36656a8..e17585cd93 100644
--- a/contrib/basic_wal_module/basic_wal_module.c
+++ b/contrib/basic_wal_module/basic_wal_module.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -58,7 +66,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_wal_module.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_wal_module", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_wal_module.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_wal_module", file)));
+ return true;
+}
diff --git a/contrib/basic_wal_module/meson.build b/contrib/basic_wal_module/meson.build
index 59939d71c4..fe68a806a9 100644
--- a/contrib/basic_wal_module/meson.build
+++ b/contrib/basic_wal_module/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_wal_module/t/001_restore.pl b/contrib/basic_wal_module/t/001_restore.pl
new file mode 100644
index 0000000000..c9f39ea413
--- /dev/null
+++ b/contrib/basic_wal_module/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_wal_module'");
+$node->append_conf('postgresql.conf', "basic_wal_module.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_wal_module'");
+$restore->append_conf('postgresql.conf', "basic_wal_module.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 12d9bb3f81..19f79fd2f2 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="wal-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-wal-module.sgml b/doc/src/sgml/basic-wal-module.sgml
index f972566374..ebb9f0d8c3 100644
--- a/doc/src/sgml/basic-wal-module.sgml
+++ b/doc/src/sgml/basic-wal-module.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_wal_module</filename> is an example of an archive module.
- This module copies completed WAL segment files to the specified directory.
- This may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="wal-modules"/>.
+ <filename>basic_wal_module</filename> is an example of a write-ahead log
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about write-ahead log modules, see
+ <xref linkend="wal-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-wal-module-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_wal_module.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_wal_module</filename> is used as an archive module,
+ server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bba24bdcb8..feb4ca34af 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3807,7 +3807,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3835,7 +3836,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3870,7 +3872,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="wal-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3915,7 +3952,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3944,7 +3984,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/doc/src/sgml/wal-modules.sgml b/doc/src/sgml/wal-modules.sgml
index 451c591e17..479b0a7d14 100644
--- a/doc/src/sgml/wal-modules.sgml
+++ b/doc/src/sgml/wal-modules.sgml
@@ -8,27 +8,33 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom WAL module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command (e.g., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
will submit completed WAL files to the module, and the server will avoid
recycling or removing these WAL files until the module indicates that the
- files were successfully archived. It is ultimately up to the module to
- decide what to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
+ were successfully archived. When a custom
+ <xref linkend="guc-restore-library"/> is configured, PostgreSQL will use the
+ module for recovery actions. It is ultimately up to the module to decide how
+ to accomplish each task, but some recommendations are listed at
+ <xref linkend="backup-archiving-wal"/> and
+ <xref linkend="backup-pitr-recovery"/>.
</para>
<para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
+ Write-ahead log modules must at least consist of an initialization function
+ (see <xref linkend="archive-module-init"/> and
+ <xref linkend="recovery-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/> and
+ <xref linkend="recovery-module-callbacks"/>). However, write-ahead log
+ modules are also permitted to do much more (e.g., declare GUCs and register
+ background workers). A module may be used for both
+ <varname>archive_library</varname> and <varname>restore_library</varname>.
</para>
<para>
@@ -43,7 +49,7 @@
</indexterm>
<sect2 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -70,6 +76,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect2>
<sect2 id="archive-module-callbacks">
@@ -136,6 +148,139 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-modules">
+ <title>Recovery Modules</title>
+ <indexterm zone="recovery-modules">
+ <primary>Recovery Modules</primary>
+ </indexterm>
+
+ <sect2 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/> as the library base name. The
+ normal library search path is used to locate the library. To provide the
+ required recovery module callbacks and to indicate that the library is
+ actually a recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect2>
+
+ <sect2 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The
+ server will call them as required to execute recovery actions.
+ </para>
+
+ <sect3 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a
+ single archived segment of the WAL file series for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point and is intended to provide a mechanism for cleaning up old
+ archived WAL files that are no longer needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the recovery module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect3>
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 8458209f49..dfd409905c 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -4,7 +4,8 @@
* Recovery functions for a user-specified shell command.
*
* These recovery functions use a user-specified shell command (e.g. based
- * on the GUC restore_command).
+ * on the GUC restore_command). It is used as the default, but other
+ * modules may define their own recovery logic.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -24,6 +25,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
@@ -31,6 +36,16 @@ static bool ExecuteRecoveryCommand(const char *command,
uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
/*
* Attempt to execute a shell-based restore command.
*
@@ -88,7 +103,7 @@ shell_restore(const char *file, const char *path,
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -104,7 +119,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fb4c860bde..0e6d2d9363 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4886,15 +4886,16 @@ static void
CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
+
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7309,14 +7310,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b89addf97..4af5689c25 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,6 +22,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -31,6 +33,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -70,7 +77,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -148,14 +155,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -602,3 +610,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 2a5352f879..04f94e4d53 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..06d36513aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3787,6 +3787,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..5641e5a3a8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -270,6 +270,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
--
2.25.1
On Mon, Jan 23, 2023 at 01:44:28PM -0800, Nathan Bossart wrote:
On Mon, Jan 23, 2023 at 11:44:57AM +0900, Michael Paquier wrote:
I updated the documentation as you suggested, and I renamed basic_archive
to basic_wal_module.
Thanks. The renaming of basic_archive to basic_wal_module looked
fine, so applied.
While looking at the docs, I found a bit confusing that the main
section of the WAL modules included the full description for the
archive modules, so I have moved that into the sect2 dedicated to the
archive modules instead, as of the attached. 0004 has been updated in
consequence, with details about the recovery bits within its own
sect2.
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("must specify restore_command when standby mode is not enabled"))); + errmsg("must specify restore_command or a restore_library that defines " + "a restore callback when standby mode is not enabled"))); This is long. Shouldn't this be split into an errdetail() to list all the options at hand?Should the errmsg() be something like "recovery parameters misconfigured"?
Hmm. Here is an idea:
- errmsg: "must specify restore option when standby mode is not enabled"
- errdetail: "Either restore_command or restore_library need to be
specified."
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0') - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("both archive_command and archive_library set"), - errdetail("Only one of archive_command, archive_library may be set."))); + CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library", + XLogArchiveCommand, "archive_command");The introduction of this routine could be a patch on its own, as it
impacts the archiving path.I moved this to a separate patch.
While pondering about that, I found a bit sad that this only works for
string GUCs, while it could be possible to do the same kind of checks
for the other GUC types with a more generic routine? Not enum,
obviously, but int, float, bool and real, with the a failure if both
GUCs are set to non-default values? Also, and I may be missing
something here, do we really need to pass the value of the parameters
to check? Wouldn't it be enough to check for the case where both
parameters are set to their non-default values after reloading?
- Startup reloading, as of StartupRereadConfig(). This code could
involve a WAL receiver restart depending on a change in the slot
change or in primary_conninfo, and force at the same time an ERROR
because of conflicting recovery library and command configuration.
This one should be safe because pendingWalRcvRestart would just be
considered later on by the startup process itself while waiting for
WAL to become available. Still this could deserve a comment? Even if
there is a misconfiguration, a reload on a standby would enforce a
FATAL in the startup process, taking down the whole server.Do you think the parameter checks should go before the WAL receiver restart
logic?
Yeah, switching the order makes the logic more robust IMO.
- Checkpointer initialization, as of CheckpointerMain(). A
configuration failure in this code path, aka server startup, causes
the server to loop infinitely on FATAL with the misconfiguration
showing up all the time.. This is a problem.Perhaps this is a reason to move the parameter check in CheckpointerMain()
to after the sigsetjmp() block. This should avoid full server restarts.
Only the checkpointer process would loop with the ERROR.
The loop part is annoying.. I've never been a fan of adding this
cross-value checks for the archiver modules in the first place, and it
would make things much simpler in the checkpointer if we need to think
about that as we want these values to be reloadable. Perhaps this
could just be an exception where we just give priority on one over the
other archive_cleanup_command? The startup process has a well-defined
sequence after a failure, while the checkpointer is designed to remain
robust.
- Last comes the checkpointer GUC reloading, as of
HandleCheckpointerInterrupts(), with a second problem. This
introduces a failure path where ConfigReloadPending is processed at
the same time as ShutdownRequestPending based on the way it is coded,
interacting with what would be a normal shutdown in some cases? And
actually, if you enforce a misconfiguration on reload, the
checkpointer reports an error but it does not enforce a process
restart, hence it keeps around the new, incorrect, configuration while
waiting for a new checkpoint to happen once restore_library and
archive_cleanup_command are set. This could lead to surprises, IMO.
Upgrading to a FATAL in this code path triggers an infinite loop, like
the startup path.If we move the parameter check in CheckpointerMain() as described above,
the checkpointer should be unable to proceed with an incorrect
configuration. For the normal shutdown part, do you think the
ShutdownRequestPending block should be moved to before the
ConfigReloadPending block in HandleCheckpointerInterrupts()?
I would not touch this order. This could influence the setup a
shutdown checkpoint relies on, for one.
If the archive_cleanup_command ballback of a restore library triggers
a FATAL, it seems to me that it would continuously trigger a server
restart, actually. Perhaps that's something to document, in
comparison to the safe fallbacks of the shell command where we don't
force an ERROR to give priority to the stability of the checkpointer.I'm not sure it's worth documenting that ereport(FATAL, ...) in the
checkpointer process will cause a server restart. In most cases, an
extension author would use ERROR, which, if we make the aforementioned
changes, would at most cause the checkpointer to effectively restart. This
is similar to archive modules where an ERROR causes only the archiver
process to restart. Also, we document that recovery libraries are loaded
in the startup and checkpointer processes, so IMO it should be relatively
apparent that doing something like FATAL or proc_exit() is bad.
Okay. Fine by me. This could always be amended later, as required.
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
Could it be cleaner to put this knowledge directly in shell_restore.c
with a fast-exit path after entering each callback? It does not
strike me as a good thing to sprinkle more than necessary the
knowledge about the commands.
Another question that popped in my mind: could it be better to have
two different shutdown callbacks for the checkpointer and the startup
process? Having some tests for both, like shell_archive.c, would be
nice, actually.
--
Michael
Attachments:
v11-0001-doc-Refactor-the-chapter-related-to-archive-modu.patchtext/x-diff; charset=us-asciiDownload
From 7d4425744e2bc96418d6ef85c9408bed9e91aa9b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 25 Jan 2023 15:39:23 +0900
Subject: [PATCH v11 1/3] doc: Refactor the chapter related to archive modules
to be WAL modules
Archive modules are now a subsection of this more generic section, and
recovery modules would be a second subsection of it.
---
doc/src/sgml/archive-modules.sgml | 136 --------------------------
doc/src/sgml/backup.sgml | 2 +-
doc/src/sgml/basic-wal-module.sgml | 2 +-
doc/src/sgml/config.sgml | 2 +-
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
doc/src/sgml/system-views.sgml | 2 +-
doc/src/sgml/wal-modules.sgml | 149 +++++++++++++++++++++++++++++
8 files changed, 155 insertions(+), 142 deletions(-)
delete mode 100644 doc/src/sgml/archive-modules.sgml
create mode 100644 doc/src/sgml/wal-modules.sgml
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
deleted file mode 100644
index 1a32006e2c..0000000000
--- a/doc/src/sgml/archive-modules.sgml
+++ /dev/null
@@ -1,136 +0,0 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
-
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
- </indexterm>
-
- <para>
- PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
- </para>
-
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
- <para>
- The <filename>contrib/basic_wal_module</filename> module contains a working
- example, which demonstrates some useful techniques.
- </para>
-
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
- </indexterm>
- <para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
-
-<programlisting>
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
-</programlisting>
-
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
-
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
-
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
-
-<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-</programlisting>
-
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
-<screen>
-WARNING: archive_mode enabled, yet archiving is not configured
-</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
-
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
-
-<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-</programlisting>
-
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
-
-<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
-</programlisting>
- </para>
- </sect2>
- </sect1>
-</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..12d9bb3f81 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -660,7 +660,7 @@ test ! -f /mnt/server/archivedir/00000001000000A900000065 && cp pg_wal/0
than writing a shell command. However, archive modules can be more
performant than archiving via shell, and they will have access to many
useful server resources. For more information about archive modules, see
- <xref linkend="archive-modules"/>.
+ <xref linkend="wal-modules"/>.
</para>
<para>
diff --git a/doc/src/sgml/basic-wal-module.sgml b/doc/src/sgml/basic-wal-module.sgml
index c418b01eb8..f972566374 100644
--- a/doc/src/sgml/basic-wal-module.sgml
+++ b/doc/src/sgml/basic-wal-module.sgml
@@ -12,7 +12,7 @@
This module copies completed WAL segment files to the specified directory.
This may not be especially useful, but it can serve as a starting point for
developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ modules, see <xref linkend="wal-modules"/>.
</para>
<para>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index f985afc009..bba24bdcb8 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3666,7 +3666,7 @@ include_dir 'conf.d'
shared library is used for archiving. The WAL archiver process is
restarted by the postmaster when this parameter changes. For more
information, see <xref linkend="backup-archiving-wal"/> and
- <xref linkend="archive-modules"/>.
+ <xref linkend="wal-modules"/>.
</para>
<para>
This parameter can only be set in the
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 2d36c34ce8..31b4dc8fba 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY wal-modules SYSTEM "wal-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..817e774155 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &wal-modules;
</part>
diff --git a/doc/src/sgml/system-views.sgml b/doc/src/sgml/system-views.sgml
index 7c8fc3f654..e93e733051 100644
--- a/doc/src/sgml/system-views.sgml
+++ b/doc/src/sgml/system-views.sgml
@@ -3351,7 +3351,7 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx
<xref linkend="guc-shared-preload-libraries"/>,
a call to a C function in the extension, or the
<link linkend="sql-load"><command>LOAD</command></link> command).
- For example, since <link linkend="archive-modules">archive modules</link>
+ For example, since <link linkend="wal-modules">archive modules</link>
are normally loaded only by the archiver process not regular sessions,
this view will not display any customized options defined by such modules
unless special action is taken to load them into the backend process
diff --git a/doc/src/sgml/wal-modules.sgml b/doc/src/sgml/wal-modules.sgml
new file mode 100644
index 0000000000..283bba782d
--- /dev/null
+++ b/doc/src/sgml/wal-modules.sgml
@@ -0,0 +1,149 @@
+<!-- doc/src/sgml/wal-modules.sgml -->
+
+<chapter id="wal-modules">
+ <title>Write-Ahead Log Modules</title>
+ <indexterm zone="wal-modules">
+ <primary>Write-Ahead Log Modules</primary>
+ </indexterm>
+
+ <para>
+ This chapter provides general information about writing WAL modules, related
+ to the manipulation of WAL segments by the backend.
+ </para>
+
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
+ </indexterm>
+
+ <para>
+ PostgreSQL provides infrastructure to create custom modules for continuous
+ archiving (see <xref linkend="continuous-archiving"/>). While archiving via
+ a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
+ simpler, a custom WAL module will often be considerably more robust and
+ performant.
+ </para>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
+ will submit completed WAL files to the module, and the server will avoid
+ recycling or removing these WAL files until the module indicates that the
+ files were successfully archived. It is ultimately up to the module to
+ decide what to do with each WAL file, but many recommendations are listed at
+ <xref linkend="backup-archiving-wal"/>.
+ </para>
+
+ <para>
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <para>
+ The <filename>contrib/basic_archive</filename> module contains a working
+ example, which demonstrates some useful techniques.
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+</programlisting>
+
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
+
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
+ <para>
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
+
+<programlisting>
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
+<screen>
+WARNING: archive_mode enabled, yet archiving is not configured
+</screen>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
+
+<programlisting>
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
+</chapter>
--
2.39.0
v11-0002-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From f6caa58074a6dd66c57f2e8a1ff36e9fbe6530b9 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 25 Jan 2023 15:54:35 +0900
Subject: [PATCH v11 2/3] introduce routine for checking mutually exclusive
parameters
---
src/include/utils/guc.h | 2 ++
src/backend/postmaster/pgarch.c | 7 ++-----
src/backend/utils/misc/guc.c | 18 ++++++++++++++++++
3 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..d7785785c2 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,8 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern void CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..8e91f2d70f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -831,11 +831,8 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ CheckMutuallyExclusiveGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command");
memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d52069f446..e63515d624 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2558,6 +2558,24 @@ ReportChangedGUCOptions(void)
}
}
+/*
+ * Check if two GUCs are mutually set. Works only when comparing two
+ * string GUCs.
+ *
+ * ERROR if both parameters are set.
+ */
+void
+CheckMutuallyExclusiveGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+}
+
/*
* ReportGUCOption: if appropriate, transmit option value to frontend
*
--
2.39.0
v11-0003-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 52407882dee0751807f528bc785925a49e88749d Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Wed, 25 Jan 2023 16:27:43 +0900
Subject: [PATCH v11 3/3] introduce restore_library
---
src/include/access/xlog_internal.h | 1 +
src/include/access/xlogarchive.h | 44 ++++-
src/include/access/xlogrecovery.h | 1 +
src/backend/access/transam/shell_restore.c | 21 ++-
src/backend/access/transam/xlog.c | 12 +-
src/backend/access/transam/xlogarchive.c | 70 ++++++-
src/backend/access/transam/xlogrecovery.c | 26 ++-
src/backend/postmaster/checkpointer.c | 26 +++
src/backend/postmaster/startup.c | 23 ++-
src/backend/utils/misc/guc_tables.c | 10 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
doc/src/sgml/backup.sgml | 43 ++++-
doc/src/sgml/basic-wal-module.sgml | 33 ++--
doc/src/sgml/config.sgml | 53 +++++-
doc/src/sgml/high-availability.sgml | 23 ++-
doc/src/sgml/wal-modules.sgml | 171 +++++++++++++++++-
contrib/basic_wal_module/Makefile | 2 +
contrib/basic_wal_module/basic_wal_module.c | 69 ++++++-
contrib/basic_wal_module/meson.build | 5 +
contrib/basic_wal_module/t/001_restore.pl | 44 +++++
20 files changed, 615 insertions(+), 63 deletions(-)
create mode 100644 contrib/basic_wal_module/t/001_restore.pl
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index 59fc7bc105..756f0898b5 100644
--- a/src/include/access/xlog_internal.h
+++ b/src/include/access/xlog_internal.h
@@ -400,5 +400,6 @@ extern PGDLLIMPORT bool ArchiveRecoveryRequested;
extern PGDLLIMPORT bool InArchiveRecovery;
extern PGDLLIMPORT bool StandbyMode;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
#endif /* XLOG_INTERNAL_H */
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 299304703e..71c9b88165 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog);
extern bool XLogArchiveIsReadyOrDone(const char *xlog);
extern void XLogArchiveCleanup(const char *xlog);
-extern bool shell_restore(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Recovery module callbacks
+ *
+ * These callback functions should be defined by recovery libraries and
+ * returned via _PG_recovery_module_init(). For more information about the
+ * purpose of each callback, refer to the recovery modules documentation.
+ */
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+typedef void (*RecoveryShutdownCB) (void);
+
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+
+extern RecoveryModuleCallbacks RecoveryContext;
+
+/*
+ * Type of the shared library symbol _PG_recovery_module_init that is looked up
+ * when loading a recovery library.
+ */
+typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_recovery_module_init(RecoveryModuleCallbacks *cb);
+
+extern void LoadRecoveryCallbacks(void);
+extern void call_recovery_module_shutdown_cb(int code, Datum arg);
+
+/*
+ * Since the logic for recovery via a shell command is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_restore_init(RecoveryModuleCallbacks *cb);
#endif /* XLOG_ARCHIVE_H */
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 47c29350f5..35d1d09374 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -55,6 +55,7 @@ extern PGDLLIMPORT int recovery_min_apply_delay;
extern PGDLLIMPORT char *PrimaryConnInfo;
extern PGDLLIMPORT char *PrimarySlotName;
extern PGDLLIMPORT char *recoveryRestoreCommand;
+extern PGDLLIMPORT char *restoreLibrary;
extern PGDLLIMPORT char *recoveryEndCommand;
extern PGDLLIMPORT char *archiveCleanupCommand;
diff --git a/src/backend/access/transam/shell_restore.c b/src/backend/access/transam/shell_restore.c
index 8458209f49..dfd409905c 100644
--- a/src/backend/access/transam/shell_restore.c
+++ b/src/backend/access/transam/shell_restore.c
@@ -4,7 +4,8 @@
* Recovery functions for a user-specified shell command.
*
* These recovery functions use a user-specified shell command (e.g. based
- * on the GUC restore_command).
+ * on the GUC restore_command). It is used as the default, but other
+ * modules may define their own recovery logic.
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
@@ -24,6 +25,10 @@
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static bool shell_restore(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void shell_archive_cleanup(const char *lastRestartPointFileName);
+static void shell_recovery_end(const char *lastRestartPointFileName);
static bool ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
@@ -31,6 +36,16 @@ static bool ExecuteRecoveryCommand(const char *command,
uint32 wait_event_info,
int fail_elevel);
+void
+shell_restore_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&shell_restore_init, RecoveryModuleInit);
+
+ cb->restore_cb = shell_restore;
+ cb->archive_cleanup_cb = shell_archive_cleanup;
+ cb->recovery_end_cb = shell_recovery_end;
+}
+
/*
* Attempt to execute a shell-based restore command.
*
@@ -88,7 +103,7 @@ shell_restore(const char *file, const char *path,
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
+static void
shell_archive_cleanup(const char *lastRestartPointFileName)
{
char *cmd;
@@ -104,7 +119,7 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
+static void
shell_recovery_end(const char *lastRestartPointFileName)
{
char *cmd;
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index fb4c860bde..47f7d2fcc1 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -4887,14 +4887,14 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
+ if (RecoveryContext.recovery_end_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RecoveryContext.recovery_end_cb(lastRestartPointFname);
}
/*
@@ -7309,14 +7309,14 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Execute the archive-cleanup callback, if any.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
+ if (RecoveryContext.archive_cleanup_cb)
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RecoveryContext.archive_cleanup_cb(lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b89addf97..4af5689c25 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -22,6 +22,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
+#include "access/xlogrecovery.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
@@ -31,6 +33,11 @@
#include "storage/ipc.h"
#include "storage/lwlock.h"
+/*
+ * Global context for recovery-related callbacks.
+ */
+RecoveryModuleCallbacks RecoveryContext;
+
/*
* Attempt to retrieve the specified file from off-line archival storage.
* If successful, fill "path" with its complete path (note that this will be
@@ -70,7 +77,7 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
goto not_available;
/*
@@ -148,14 +155,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
/*
- * Check signals before restore command and reset afterwards.
+ * Check signals before restore callback and reset afterwards.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- ret = shell_restore(xlogfname, xlogpath, lastRestartPointFname);
+ ret = RecoveryContext.restore_cb(xlogfname, xlogpath,
+ lastRestartPointFname);
PostRestoreCommand();
@@ -602,3 +610,59 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the recovery callbacks into our global RecoveryContext. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRecoveryCallbacks(void)
+{
+ RecoveryModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_recovery_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RecoveryModuleInit)
+ load_external_function(restoreLibrary, "_PG_recovery_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("recovery modules have to define the symbol "
+ "_PG_recovery_module_init")));
+
+ memset(&RecoveryContext, 0, sizeof(RecoveryModuleCallbacks));
+ (*init) (&RecoveryContext);
+
+ /*
+ * If using shell commands, remove callbacks for any commands that are not
+ * set.
+ */
+ if (restoreLibrary[0] == '\0')
+ {
+ if (recoveryRestoreCommand[0] == '\0')
+ RecoveryContext.restore_cb = NULL;
+ if (archiveCleanupCommand[0] == '\0')
+ RecoveryContext.archive_cleanup_cb = NULL;
+ if (recoveryEndCommand[0] == '\0')
+ RecoveryContext.recovery_end_cb = NULL;
+ }
+}
+
+/*
+ * Call the shutdown callback of the loaded recovery module, if defined.
+ */
+void
+call_recovery_module_shutdown_cb(int code, Datum arg)
+{
+ if (RecoveryContext.shutdown_cb)
+ RecoveryContext.shutdown_cb();
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 2a5352f879..04f94e4d53 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -80,6 +80,7 @@ const struct config_enum_entry recovery_target_action_options[] = {
/* options formerly taken from recovery.conf for archive recovery */
char *recoveryRestoreCommand = NULL;
+char *restoreLibrary = NULL;
char *recoveryEndCommand = NULL;
char *archiveCleanupCommand = NULL;
RecoveryTargetType recoveryTarget = RECOVERY_TARGET_UNSET;
@@ -1053,24 +1054,37 @@ validateRecoveryParameters(void)
if (!ArchiveRecoveryRequested)
return;
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Check for compulsory parameters
*/
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ RecoveryContext.restore_cb == NULL)
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
- errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
+ (errmsg("specified neither primary_conninfo nor restore_command "
+ "nor a restore_library that defines a restore callback"),
+ errhint("The database server will regularly poll the pg_wal "
+ "subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (RecoveryContext.restore_cb == NULL)
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a restore_library that defines "
+ "a restore callback when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index de0bbbfa79..6350fd0b83 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -38,6 +38,7 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "libpq/pqsignal.h"
#include "miscadmin.h"
@@ -222,6 +223,16 @@ CheckpointerMain(void)
*/
before_shmem_exit(pgstat_before_server_shutdown, 0);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks. We do this before setting up the exception handler
+ * so that any problems result in a server crash shortly after startup.
+ */
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ before_shmem_exit(call_recovery_module_shutdown_cb, 0);
+ LoadRecoveryCallbacks();
+
/*
* Create a memory context that we will do all our work in. We do this so
* that we can reset the context during error recovery and thereby avoid
@@ -548,6 +559,9 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +577,18 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8786186898..f9ff2b5583 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -20,6 +20,7 @@
#include "postgres.h"
#include "access/xlog.h"
+#include "access/xlogarchive.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
#include "libpq/pqsignal.h"
@@ -133,13 +134,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the recovery callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -161,6 +166,22 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command");
+ CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command");
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_recovery_module_shutdown_cb(0, (Datum) 0);
+ LoadRecoveryCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..06d36513aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -3787,6 +3787,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for recovery actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..5641e5a3a8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -270,6 +270,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for recovery actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 12d9bb3f81..19f79fd2f2 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,27 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
- which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>
+ that defines a restore callback, which tells
+ <productname>PostgreSQL</productname> how to retrieve archived WAL file
+ segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ recovery modules can be more performant than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="wal-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1219,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore function returns
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore function provided by the
+ <varname>restore_library</varname> emits an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1256,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-wal-module.sgml b/doc/src/sgml/basic-wal-module.sgml
index f972566374..ebb9f0d8c3 100644
--- a/doc/src/sgml/basic-wal-module.sgml
+++ b/doc/src/sgml/basic-wal-module.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_wal_module</filename> is an example of an archive module.
- This module copies completed WAL segment files to the specified directory.
- This may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="wal-modules"/>.
+ <filename>basic_wal_module</filename> is an example of a write-ahead log
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and recovery modules. For
+ more information about write-ahead log modules, see
+ <xref linkend="wal-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a recovery module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-wal-module-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,7 +65,8 @@ basic_wal_module.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
- Server crashes may leave temporary files with the prefix
+ When <filename>basic_wal_module</filename> is used as an archive module,
+ server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
remove such files while the server is running as long as they are unrelated
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index bba24bdcb8..feb4ca34af 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3807,7 +3807,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3835,7 +3836,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3870,7 +3872,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for recovery actions, including retrieving archived
+ segments of the WAL file series and executing tasks at restartpoints
+ and at recovery end. Either <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname> or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for recovery.
+ For more information, see <xref linkend="wal-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3915,7 +3952,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3944,7 +3984,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..6266e2df7f 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,9 +639,11 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
- reaches the end of WAL available there and <varname>restore_command</varname>
- fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s restore callback.
+ Once it reaches the end of WAL available there and
+ <varname>restore_command</varname> or the restore callback fails, it tries
+ to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
from the last valid record found in archive or <filename>pg_wal</filename>. If that fails
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and restore callbacks provided by
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s
+ <function>archive_cleanup_cb</function> callback function to remove files
+ that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/doc/src/sgml/wal-modules.sgml b/doc/src/sgml/wal-modules.sgml
index 283bba782d..7607355696 100644
--- a/doc/src/sgml/wal-modules.sgml
+++ b/doc/src/sgml/wal-modules.sgml
@@ -11,6 +11,10 @@
to the manipulation of WAL segments by the backend.
</para>
+ <para>
+ A single module may be used for both archive and recovery modules.
+ </para>
+
<sect1 id="archive-modules">
<title>Archive Modules</title>
<indexterm zone="archive-modules">
@@ -48,7 +52,7 @@
</para>
<sect2 id="archive-module-init">
- <title>Initialization Functions</title>
+ <title>Archive Module Initialization Functions</title>
<indexterm zone="archive-module-init">
<primary>_PG_archive_module_init</primary>
</indexterm>
@@ -75,6 +79,12 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect2>
<sect2 id="archive-module-callbacks">
@@ -141,6 +151,165 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
<programlisting>
typedef void (*ArchiveShutdownCB) (void);
+</programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
+
+ <sect1 id="recovery-modules">
+ <title>Recovery Modules</title>
+ <indexterm zone="recovery-modules">
+ <primary>Recovery Modules</primary>
+ </indexterm>
+
+ <para>
+ PostgreSQL provides infrastructure to create custom modules for
+ recovery (see <xref linkend="continuous-archiving"/>). While
+ a shell command like <xref linkend="guc-restore-command"/>) is
+ much simpler, a custom module will often be considerably more robust
+ and performant.
+ </para>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for recovery actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Write-ahead log modules must at least consist of an initialization
+ function
+ (see <xref linkend="recovery-module-init"/>) and the required callbacks
+ (see <xref linkend="recovery-module-callbacks"/>). However, write-ahead
+ log modules are also permitted to do much more (e.g., declare GUCs and
+ register background workers).
+ </para>
+
+ <sect2 id="recovery-module-init">
+ <title>Recovery Module Initialization Functions</title>
+ <indexterm zone="recovery-module-init">
+ <primary>_PG_recovery_module_init</primary>
+ </indexterm>
+ <para>
+ A recovery library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/> as the library base name. The
+ normal library search path is used to locate the library. To provide the
+ required recovery module callbacks and to indicate that the library is
+ actually a recovery module, it needs to provide a function named
+ <function>_PG_recovery_module_init</function>. This function is passed a
+ struct that needs to be filled with the callback function pointers for
+ individual actions.
+
+<programlisting>
+typedef struct RecoveryModuleCallbacks
+{
+ RecoveryRestoreCB restore_cb;
+ RecoveryArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndCB recovery_end_cb;
+ RecoveryShutdownCB shutdown_cb;
+} RecoveryModuleCallbacks;
+typedef void (*RecoveryModuleInit) (struct RecoveryModuleCallbacks *cb);
+</programlisting>
+
+ The <function>restore_cb</function> callback is required for archive
+ recovery, but it is optional for streaming replication. The others are
+ always optional.
+ </para>
+
+ <note>
+ <para>
+ <varname>restore_library</varname> is only loaded in the startup and
+ checkpointer processes and in single-user mode.
+ </para>
+ </note>
+ </sect2>
+
+ <sect2 id="recovery-module-callbacks">
+ <title>Recovery Module Callbacks</title>
+ <para>
+ The recovery callbacks define the actual behavior of the module. The
+ server will call them as required to execute recovery actions.
+ </para>
+
+ <sect3 id="recovery-module-restore">
+ <title>Restore Callback</title>
+ <para>
+ The <function>restore_cb</function> callback is called to retrieve a
+ single archived segment of the WAL file series for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="recovery-module-restore"><function>restore_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="recovery-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the recovery module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations.
+
+<programlisting>
+typedef void (*RecoveryShutdownCB) (void);
</programlisting>
</para>
</sect3>
diff --git a/contrib/basic_wal_module/Makefile b/contrib/basic_wal_module/Makefile
index 1f88aaf469..b92126ff52 100644
--- a/contrib/basic_wal_module/Makefile
+++ b/contrib/basic_wal_module/Makefile
@@ -9,6 +9,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_wal_module/basic_wal_mo
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_wal_module/basic_wal_module.c b/contrib/basic_wal_module/basic_wal_module.c
index 78c36656a8..e17585cd93 100644
--- a/contrib/basic_wal_module/basic_wal_module.c
+++ b/contrib/basic_wal_module/basic_wal_module.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -30,6 +35,7 @@
#include <sys/time.h>
#include <unistd.h>
+#include "access/xlogarchive.h"
#include "common/int.h"
#include "miscadmin.h"
#include "postmaster/pgarch.h"
@@ -48,6 +54,8 @@ static bool basic_archive_file(const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
/*
* _PG_init
@@ -58,7 +66,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_wal_module.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -87,6 +95,19 @@ _PG_archive_module_init(ArchiveModuleCallbacks *cb)
cb->archive_file_cb = basic_archive_file;
}
+/*
+ * _PG_recovery_module_init
+ *
+ * Returns the module's restore callback.
+ */
+void
+_PG_recovery_module_init(RecoveryModuleCallbacks *cb)
+{
+ AssertVariableIsOfType(&_PG_recovery_module_init, RecoveryModuleInit);
+
+ cb->restore_cb = basic_restore_file;
+}
+
/*
* check_archive_directory
*
@@ -99,8 +120,8 @@ check_archive_directory(char **newval, void **extra, GucSource source)
/*
* The default value is an empty string, so we have to accept that value.
- * Our check_configured callback also checks for this and prevents
- * archiving from proceeding if it is still empty.
+ * Our check_configured and restore callbacks also check for this and
+ * prevent archiving or recovery from proceeding if it is still empty.
*/
if (*newval == NULL || *newval[0] == '\0')
return true;
@@ -368,3 +389,45 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file from the WAL archives.
+ */
+static bool
+basic_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char source[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_wal_module", file)));
+
+ if (archive_directory == NULL || archive_directory[0] == '\0')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"basic_wal_module.archive_directory\" is not set")));
+
+ /*
+ * Check whether the file exists. If not, we return false to indicate that
+ * there are no more files to restore.
+ */
+ snprintf(source, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(source, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m", source)));
+ return false;
+ }
+
+ copy_file(source, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_wal_module", file)));
+ return true;
+}
diff --git a/contrib/basic_wal_module/meson.build b/contrib/basic_wal_module/meson.build
index 59939d71c4..fe68a806a9 100644
--- a/contrib/basic_wal_module/meson.build
+++ b/contrib/basic_wal_module/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_wal_module/t/001_restore.pl b/contrib/basic_wal_module/t/001_restore.pl
new file mode 100644
index 0000000000..c9f39ea413
--- /dev/null
+++ b/contrib/basic_wal_module/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_wal_module'");
+$node->append_conf('postgresql.conf', "basic_wal_module.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_wal_module'");
+$restore->append_conf('postgresql.conf', "basic_wal_module.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
--
2.39.0
On Wed, Jan 25, 2023 at 04:34:21PM +0900, Michael Paquier wrote:
The loop part is annoying.. I've never been a fan of adding this
cross-value checks for the archiver modules in the first place, and it
would make things much simpler in the checkpointer if we need to think
about that as we want these values to be reloadable. Perhaps this
could just be an exception where we just give priority on one over the
other archive_cleanup_command? The startup process has a well-defined
sequence after a failure, while the checkpointer is designed to remain
robust.
Yeah, there are some problems here. If we ERROR, we'll just bounce back to
the sigsetjmp() block once a second, and we'll never pick up configuration
reloads, shutdown signals, etc. If we FATAL, we'll just rapidly restart
over and over. Given the dicussion about misconfigured archiving
parameters [0]/messages/by-id/9ee5d180-2c32-a1ca-d3d7-63a723f68d9a@enterprisedb.com, I doubt folks will be okay with giving priority to one or
the other.
I'm currently thinking that the checkpointer should set a flag and clear
the recovery callbacks when a misconfiguration is detected. Anytime the
checkpointer tries to use the archive-cleanup callback, a WARNING would be
emitted. This is similar to an approach I proposed for archiving
misconfigurations (that we didn't proceed with) [1]/messages/by-id/20220914222736.GA3042279@nathanxps13. Given the
aformentioned problems, this approach might be more suitable for the
checkpointer than it is for the archiver.
Thoughts?
[0]: /messages/by-id/9ee5d180-2c32-a1ca-d3d7-63a723f68d9a@enterprisedb.com
[1]: /messages/by-id/20220914222736.GA3042279@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Jan 26, 2023 at 09:40:58PM -0800, Nathan Bossart wrote:
On Wed, Jan 25, 2023 at 04:34:21PM +0900, Michael Paquier wrote:
The loop part is annoying.. I've never been a fan of adding this
cross-value checks for the archiver modules in the first place, and it
would make things much simpler in the checkpointer if we need to think
about that as we want these values to be reloadable. Perhaps this
could just be an exception where we just give priority on one over the
other archive_cleanup_command? The startup process has a well-defined
sequence after a failure, while the checkpointer is designed to remain
robust.Yeah, there are some problems here. If we ERROR, we'll just bounce back to
the sigsetjmp() block once a second, and we'll never pick up configuration
reloads, shutdown signals, etc. If we FATAL, we'll just rapidly restart
over and over. Given the dicussion about misconfigured archiving
parameters [0], I doubt folks will be okay with giving priority to one or
the other.I'm currently thinking that the checkpointer should set a flag and clear
the recovery callbacks when a misconfiguration is detected. Anytime the
checkpointer tries to use the archive-cleanup callback, a WARNING would be
emitted. This is similar to an approach I proposed for archiving
misconfigurations (that we didn't proceed with) [1]. Given the
aformentioned problems, this approach might be more suitable for the
checkpointer than it is for the archiver.
The more I think about this, the more I wonder whether we really need to
include archive_cleanup_command and recovery_end_command in recovery
modules. Another weird thing with the checkpointer is that the
restore_library will stay loaded long after recovery is finished, and it'll
be loaded regardless of whether recovery is required in the first place.
Of course, that typically won't cause any problems, and we could wait until
we need to do archive cleanup to load the library (and call its shutdown
callback when recovery is finished), but this strikes me as potentially
more complexity than the feature is worth. Perhaps we should just focus on
covering the restore_command functionality for now and add the rest later.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Jan 26, 2023 at 09:40:58PM -0800, Nathan Bossart wrote:
Yeah, there are some problems here. If we ERROR, we'll just bounce back to
the sigsetjmp() block once a second, and we'll never pick up configuration
reloads, shutdown signals, etc. If we FATAL, we'll just rapidly restart
over and over. Given the dicussion about misconfigured archiving
parameters [0], I doubt folks will be okay with giving priority to one or
the other.I'm currently thinking that the checkpointer should set a flag and clear
the recovery callbacks when a misconfiguration is detected. Anytime the
checkpointer tries to use the archive-cleanup callback, a WARNING would be
emitted. This is similar to an approach I proposed for archiving
misconfigurations (that we didn't proceed with) [1]. Given the
aformentioned problems, this approach might be more suitable for the
checkpointer than it is for the archiver.
So, by doing that, archive_library would be ignored. What should be
the checkpointer do when a aconfiguration error is found on
misconfiguration? Would archive_cleanup_command be equally ignored or
could there be a risk to see it used by the checkpointer?
Ignoring it would be fine as the user intended the use of a library,
rather than enforcing its use via a value one did not really want.
So, on top of cleaning the callbacks, archive_cleanup_command should
also be cleaned up in the checkpointer? Issuing one WARNING per
checkpoint would be indeed much better than looping over and over,
impacting the system reliability.
Thoughts or comments from anyone?
--
Michael
Hi,
On 2023-01-27 15:28:21 -0800, Nathan Bossart wrote:
The more I think about this, the more I wonder whether we really need to
include archive_cleanup_command and recovery_end_command in recovery
modules.
I think it would be hard to write a good module that isn't just implementing
the existing commands without it. Needing to clean up archives and reacting to
the end of recovery seems a pretty core task.
Another weird thing with the checkpointer is that the restore_library will
stay loaded long after recovery is finished, and it'll be loaded regardless
of whether recovery is required in the first place.
I don't see a problem with that. And I suspect we might even end up there
for other reasons.
I was briefly wondering whether it'd be worth trying to offload things like
archive_cleanup_command from checkpointer to a different process, for
robustness. But given that it's pretty much required for performance that the
module runs in the startup process, that ship probably has sailed.
Greetings,
Andres Freund
Hi,
On 2023-01-25 16:34:21 +0900, Michael Paquier wrote:
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h index 299304703e..71c9b88165 100644 --- a/src/include/access/xlogarchive.h +++ b/src/include/access/xlogarchive.h @@ -30,9 +30,45 @@ extern bool XLogArchiveIsReady(const char *xlog); extern bool XLogArchiveIsReadyOrDone(const char *xlog); extern void XLogArchiveCleanup(const char *xlog);-extern bool shell_restore(const char *file, const char *path, - const char *lastRestartPointFileName); -extern void shell_archive_cleanup(const char *lastRestartPointFileName); -extern void shell_recovery_end(const char *lastRestartPointFileName); +/* + * Recovery module callbacks + * + * These callback functions should be defined by recovery libraries and + * returned via _PG_recovery_module_init(). For more information about the + * purpose of each callback, refer to the recovery modules documentation. + */ +typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, + const char *lastRestartPointFileName); +typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryShutdownCB) (void);
I think the signature of these forces bad coding practices, because there's no
way to have state within a recovery module (as there's no parameter for it).
It's possible we would eventually support multiple modules, e.g. restoring
from shorter term file based archiving and from longer term archiving in some
blob store. Then we'll regret not having a varible for this.
+typedef struct RecoveryModuleCallbacks +{ + RecoveryRestoreCB restore_cb; + RecoveryArchiveCleanupCB archive_cleanup_cb; + RecoveryEndCB recovery_end_cb; + RecoveryShutdownCB shutdown_cb; +} RecoveryModuleCallbacks; + +extern RecoveryModuleCallbacks RecoveryContext;
I think that'll typically be interpreteted as a MemoryContext by readers.
Also, why is this a global var? Exported too?
+/* + * Type of the shared library symbol _PG_recovery_module_init that is looked up + * when loading a recovery library. + */ +typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);
I think this is a bad way to return callbacks. This way the
RecoveryModuleCallbacks needs to be modifiable, which makes the job for the
compiler harder (and isn't the greatest for security).
I strongly encourage you to follow the model used e.g. by tableam. The init
function should return a pointer to a *constant* struct. Which is compile-time
initialized with the function pointers.
See the bottom of heapam_handler.c for how that ends up looking.
+void +LoadRecoveryCallbacks(void) +{ + RecoveryModuleInit init; + + /* + * If the shell command is enabled, use our special initialization + * function. Otherwise, load the library and call its + * _PG_recovery_module_init(). + */ + if (restoreLibrary[0] == '\0') + init = shell_restore_init; + else + init = (RecoveryModuleInit) + load_external_function(restoreLibrary, "_PG_recovery_module_init", + false, NULL);
Why a special rule for shell, instead of just defaulting the GUC to it?
+ /* + * If using shell commands, remove callbacks for any commands that are not + * set. + */ + if (restoreLibrary[0] == '\0') + { + if (recoveryRestoreCommand[0] == '\0') + RecoveryContext.restore_cb = NULL; + if (archiveCleanupCommand[0] == '\0') + RecoveryContext.archive_cleanup_cb = NULL; + if (recoveryEndCommand[0] == '\0') + RecoveryContext.recovery_end_cb = NULL;
I'd just mandate that these are implemented and that the module has to handle
if it doesn't need to do anything.
+ /* + * Check for invalid combinations of the command/library parameters and + * load the callbacks. + */ + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryRestoreCommand, "restore_command"); + CheckMutuallyExclusiveGUCs(restoreLibrary, "restore_library", + recoveryEndCommand, "recovery_end_command"); + before_shmem_exit(call_recovery_module_shutdown_cb, 0); + LoadRecoveryCallbacks();
This kind of sequence is duplicated into several places.
Greetings,
Andres Freund
On Fri, Jan 27, 2023 at 04:09:39PM -0800, Andres Freund wrote:
I think it would be hard to write a good module that isn't just implementing
the existing commands without it. Needing to clean up archives and reacting to
the end of recovery seems a pretty core task.
FWIW, recovery_end_command is straight-forward as it is done by the
startup process, so that's an easy take. You could split the cake
into two parts, as well, aka first focus on restore_command and
recovery_end_command as a first step, then we could try to figure out
how archive_cleanup_command would fit in this picture with the
checkpointer or a different process. There are a bunch of deployments
where WAL archive retention is controlled by the age of the backups,
not by the backend deciding when these should be removed as a
checkpoint runs depending on the computed redo LSN, so recovery
modules would still be useful with just coverage for restore_command
and recovery_end_command.
I was briefly wondering whether it'd be worth trying to offload things like
archive_cleanup_command from checkpointer to a different process, for
robustness. But given that it's pretty much required for performance that the
module runs in the startup process, that ship probably has sailed.
Yeah, agreed that this could be interesting. That could leverage the
work of the checkpointer. Nathan has proposed a patch for that
recently, as far as I recall, to offload some tasks from the startup
and checkpointer processes:
https://commitfest.postgresql.org/41/3448/
So that pretty much goes into the same area?
--
Michael
On Fri, Jan 27, 2023 at 04:23:19PM -0800, Andres Freund wrote:
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, + const char *lastRestartPointFileName); +typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryShutdownCB) (void);I think the signature of these forces bad coding practices, because there's no
way to have state within a recovery module (as there's no parameter for it).It's possible we would eventually support multiple modules, e.g. restoring
from shorter term file based archiving and from longer term archiving in some
blob store. Then we'll regret not having a varible for this.
Are you suggesting that we add a "void *arg" to each one of these? Or put
the arguments into a struct? Or something else?
+extern RecoveryModuleCallbacks RecoveryContext;
I think that'll typically be interpreteted as a MemoryContext by readers.
How about RecoveryCallbacks?
Also, why is this a global var? Exported too?
It's needed in xlog.c, xlogarchive.c, and xlogrecovery.c. Would you rather
it be static to xlogarchive.c and provide accessors for the others?
+/* + * Type of the shared library symbol _PG_recovery_module_init that is looked up + * when loading a recovery library. + */ +typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);I think this is a bad way to return callbacks. This way the
RecoveryModuleCallbacks needs to be modifiable, which makes the job for the
compiler harder (and isn't the greatest for security).I strongly encourage you to follow the model used e.g. by tableam. The init
function should return a pointer to a *constant* struct. Which is compile-time
initialized with the function pointers.See the bottom of heapam_handler.c for how that ends up looking.
Hm. I used the existing strategy for archive modules and logical decoding
output plugins here. I think it would be weird for the archive module and
recovery module interfaces to look so different, but if that's okay, I can
change it.
+void +LoadRecoveryCallbacks(void) +{ + RecoveryModuleInit init; + + /* + * If the shell command is enabled, use our special initialization + * function. Otherwise, load the library and call its + * _PG_recovery_module_init(). + */ + if (restoreLibrary[0] == '\0') + init = shell_restore_init; + else + init = (RecoveryModuleInit) + load_external_function(restoreLibrary, "_PG_recovery_module_init", + false, NULL);Why a special rule for shell, instead of just defaulting the GUC to it?
I'm not following this one. The default value of the restore_library GUC
is an empty string, which means that the shell commands should be used.
+ /* + * If using shell commands, remove callbacks for any commands that are not + * set. + */ + if (restoreLibrary[0] == '\0') + { + if (recoveryRestoreCommand[0] == '\0') + RecoveryContext.restore_cb = NULL; + if (archiveCleanupCommand[0] == '\0') + RecoveryContext.archive_cleanup_cb = NULL; + if (recoveryEndCommand[0] == '\0') + RecoveryContext.recovery_end_cb = NULL;I'd just mandate that these are implemented and that the module has to handle
if it doesn't need to do anything.
Wouldn't this just force module authors to write empty functions for the
parts they don't need?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Hi,
On 2023-01-27 16:59:10 -0800, Nathan Bossart wrote:
On Fri, Jan 27, 2023 at 04:23:19PM -0800, Andres Freund wrote:
+typedef bool (*RecoveryRestoreCB) (const char *file, const char *path, + const char *lastRestartPointFileName); +typedef void (*RecoveryArchiveCleanupCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryEndCB) (const char *lastRestartPointFileName); +typedef void (*RecoveryShutdownCB) (void);I think the signature of these forces bad coding practices, because there's no
way to have state within a recovery module (as there's no parameter for it).It's possible we would eventually support multiple modules, e.g. restoring
from shorter term file based archiving and from longer term archiving in some
blob store. Then we'll regret not having a varible for this.Are you suggesting that we add a "void *arg" to each one of these?
Yes. Or pass a pointer to a struct with a "private_data" style field to all
of them.
+extern RecoveryModuleCallbacks RecoveryContext;
I think that'll typically be interpreteted as a MemoryContext by readers.
How about RecoveryCallbacks?
Callbacks is better than Context here imo, but I think 'Recovery' makes it
sound like this actually performs WAL replay or such. Seems like it should be
RestoreCallbacks at the very least?
Also, why is this a global var? Exported too?
It's needed in xlog.c, xlogarchive.c, and xlogrecovery.c. Would you rather
it be static to xlogarchive.c and provide accessors for the others?
Maybe? Something about this feels wrong to me, but I can't entirely put my
finger on it.
+/* + * Type of the shared library symbol _PG_recovery_module_init that is looked up + * when loading a recovery library. + */ +typedef void (*RecoveryModuleInit) (RecoveryModuleCallbacks *cb);I think this is a bad way to return callbacks. This way the
RecoveryModuleCallbacks needs to be modifiable, which makes the job for the
compiler harder (and isn't the greatest for security).I strongly encourage you to follow the model used e.g. by tableam. The init
function should return a pointer to a *constant* struct. Which is compile-time
initialized with the function pointers.See the bottom of heapam_handler.c for how that ends up looking.
Hm. I used the existing strategy for archive modules and logical decoding
output plugins here.
Unfortunately I didn't realize the problem when I was designing the output
plugin interface. But there's probably too many users of it out there now to
change it.
The interface does at least provide a way to have its own "per instance"
state, via the startup callback and LogicalDecodingContext->output_plugin_private.
The worst interface in this area is index AMs - the handler returns a pointer
to a palloced struct with callbacks. That then is copied into a new allocation
in the relcache entry. We have hundreds to thousands of copies of what
bthandler() sets up in memory. Without any sort of benefit.
I think it would be weird for the archive module and
recovery module interfaces to look so different, but if that's okay, I can
change it.
I'm a bit sad about the archive module case - I wonder if we should change it
now, there can't be many users of it out there. And I think it's more likely
that we'll eventually want multiple archiving scripts to run concurrently -
which will be quite hard with the current interface (no private state).
Just btw: It's imo a bit awkward for the definition of the archiving plugin
interface to be in pgarch.h: "Exports from postmaster/pgarch.c" doesn't
describe that well. A dedicated header seems cleaner.
+void +LoadRecoveryCallbacks(void) +{ + RecoveryModuleInit init; + + /* + * If the shell command is enabled, use our special initialization + * function. Otherwise, load the library and call its + * _PG_recovery_module_init(). + */ + if (restoreLibrary[0] == '\0') + init = shell_restore_init; + else + init = (RecoveryModuleInit) + load_external_function(restoreLibrary, "_PG_recovery_module_init", + false, NULL);Why a special rule for shell, instead of just defaulting the GUC to it?
I'm not following this one. The default value of the restore_library GUC
is an empty string, which means that the shell commands should be used.
I was wondering why we implement "shell" via a separate mechanism from
restore_library. I.e. a) why doesn't restore_library default to 'shell',
instead of an empty string, b) why aren't restore_command et al implemented
using a restore module.
+ /* + * If using shell commands, remove callbacks for any commands that are not + * set. + */ + if (restoreLibrary[0] == '\0') + { + if (recoveryRestoreCommand[0] == '\0') + RecoveryContext.restore_cb = NULL; + if (archiveCleanupCommand[0] == '\0') + RecoveryContext.archive_cleanup_cb = NULL; + if (recoveryEndCommand[0] == '\0') + RecoveryContext.recovery_end_cb = NULL;I'd just mandate that these are implemented and that the module has to handle
if it doesn't need to do anything.Wouldn't this just force module authors to write empty functions for the
parts they don't need?
Yes. But what's the point of a restore library that doesn't implement a
restore command? Making some/all callbacks mandatory and validating mandatory
callbacks are set, during load, IME makes it easier to evolve the interface
over time, because problems become immediately apparent, rather than having to
wait for a certain callback to be hit.
It's not actually clear to me why another restore library shouldn't be able to
use restore_command etc, given that we have the parameters. One quite useful
module would be a version of the "shell" interface that runs multiple restore
commands in parallel, assuming we'll need subsequent files as well.
The fact that restore_command are not run in parallel, and that many useful
restore commands have a fair bit of latency, is an issue. So a shell_parallel
restore library would e.g. be useful?
Greetings,
Andres Freund
On Fri, Jan 27, 2023 at 05:55:42PM -0800, Andres Freund wrote:
On 2023-01-27 16:59:10 -0800, Nathan Bossart wrote:
I think it would be weird for the archive module and
recovery module interfaces to look so different, but if that's okay, I can
change it.I'm a bit sad about the archive module case - I wonder if we should change it
now, there can't be many users of it out there. And I think it's more likely
that we'll eventually want multiple archiving scripts to run concurrently -
which will be quite hard with the current interface (no private state).
I'm open to that. IIUC it wouldn't require too many changes to existing
archive modules, and if it gets us closer to batching or parallelism, it's
probably worth doing sooner than later.
I was wondering why we implement "shell" via a separate mechanism from
restore_library. I.e. a) why doesn't restore_library default to 'shell',
instead of an empty string, b) why aren't restore_command et al implemented
using a restore module.
I think that's the long-term idea. For archive modules, there were
concerns about backward compatibility [0]/messages/by-id/CABUevEx8cKy=+YQU_3NaeXnZV2bSB7Lk6EE+-FEcmE4JO4V1hg@mail.gmail.com.
[0]: /messages/by-id/CABUevEx8cKy=+YQU_3NaeXnZV2bSB7Lk6EE+-FEcmE4JO4V1hg@mail.gmail.com
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Fri, Jan 27, 2023 at 08:17:46PM -0800, Nathan Bossart wrote:
On Fri, Jan 27, 2023 at 05:55:42PM -0800, Andres Freund wrote:
On 2023-01-27 16:59:10 -0800, Nathan Bossart wrote:
I think it would be weird for the archive module and
recovery module interfaces to look so different, but if that's okay, I can
change it.I'm a bit sad about the archive module case - I wonder if we should change it
now, there can't be many users of it out there. And I think it's more likely
that we'll eventually want multiple archiving scripts to run concurrently -
which will be quite hard with the current interface (no private state).I'm open to that. IIUC it wouldn't require too many changes to existing
archive modules, and if it gets us closer to batching or parallelism, it's
probably worth doing sooner than later.
Here is a work-in-progress patch set for adjusting the archive modules
interface. Is this roughly what you had in mind?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From f865032283d0c937ff1c47441d70a2b11e89d3f4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH 1/4] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..36800127e8 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -416,8 +416,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -518,7 +518,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -824,7 +824,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -837,7 +837,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -854,9 +854,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -869,6 +869,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
0002-move-archive-module-exports-to-dedicated-header.patchtext/x-diff; charset=us-asciiDownload
From 1ea6fb293a1e69e6fdd39e1d6b96a0af601d8542 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH 2/4] move archive module exports to dedicated header
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 1 +
src/backend/postmaster/shell_archive.c | 2 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 54 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 ------------------
6 files changed, 58 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 36800127e8..eca02d5e74 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,6 +34,7 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "storage/fd.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..b64297e3bb 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,7 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4ac808ed22..5ebec364a2 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..099050c1ca
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
0003-restructure-archive-module-API.patchtext/x-diff; charset=us-asciiDownload
From 92eab334ce94b4e5e8dba5189ce2ffbecdeb67e6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:57:13 -0800
Subject: [PATCH 3/4] restructure archive module API
---
contrib/basic_archive/basic_archive.c | 13 +++++++++----
src/backend/postmaster/pgarch.c | 18 ++++++++---------
src/backend/postmaster/shell_archive.c | 14 ++++++++-----
src/include/postmaster/archive_module.h | 26 ++++++++++---------------
4 files changed, 36 insertions(+), 35 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..1655f72b5b 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -49,6 +49,12 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -78,13 +84,12 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
}
/*
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index eca02d5e74..627c579f0e 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -98,7 +98,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
/*
@@ -417,8 +417,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -519,7 +519,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -838,8 +838,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -855,9 +853,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -870,6 +868,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb();
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index b64297e3bb..dde20c83de 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -26,14 +26,18 @@ static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
static void shell_archive_shutdown(void);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index 099050c1ca..67b5624874 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -18,37 +18,31 @@
extern PGDLLIMPORT char *XLogArchiveLibrary;
/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
+ * API struct for an archive module. This should be allocated as a static
+ * const struct and returned via _PG_archive_module_init. archive_file_cb is
+ * the only required callback. For more information about the purpose of each
+ * callback, refer to the archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
typedef struct ArchiveModuleCallbacks
{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
+ bool (*check_configured_cb) (void);
+ bool (*archive_file_cb) (const char *file, const char *path);
+ void (*shutdown_cb) (void);
} ArchiveModuleCallbacks;
/*
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
/*
* Since the logic for archiving via a shell command is in the core server
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _ARCHIVE_MODULE_H */
--
2.25.1
0004-add-private-state-to-archive-modules.patchtext/x-diff; charset=us-asciiDownload
From 5abe0ba43ca079a0af5905ed617795e885be81c9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 22:19:57 -0800
Subject: [PATCH 4/4] add private state to archive modules
---
contrib/basic_archive/basic_archive.c | 27 ++++++++++++-------------
src/backend/postmaster/pgarch.c | 9 +++++----
src/backend/postmaster/shell_archive.c | 14 ++++++-------
src/include/postmaster/archive_module.h | 12 +++++------
4 files changed, 31 insertions(+), 31 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 1655f72b5b..fefa79debc 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -41,10 +41,9 @@
PG_MODULE_MAGIC;
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static bool basic_archive_configured(void *arg);
+static bool basic_archive_file(const char *file, const char *path, void *arg);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
@@ -73,22 +72,22 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
* _PG_archive_module_init
*
- * Returns the module's archiving callbacks.
+ * Returns the module's archiving callbacks and initializes private state.
*/
const ArchiveModuleCallbacks *
-_PG_archive_module_init(void)
+_PG_archive_module_init(void **arg)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
+ (*arg) = (void *) AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+
return &basic_archive_callbacks;
}
@@ -140,7 +139,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(void *arg)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -151,7 +150,7 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(const char *file, const char *path, void *arg)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
@@ -161,7 +160,7 @@ basic_archive_file(const char *file, const char *path)
* we can easily reset it during error recovery (thus avoiding memory
* leaks).
*/
- oldcontext = MemoryContextSwitchTo(basic_archive_context);
+ oldcontext = MemoryContextSwitchTo((MemoryContext) arg);
/*
* Since the archiver operates at the bottom of the exception stack,
@@ -188,7 +187,7 @@ basic_archive_file(const char *file, const char *path)
/* Reset our memory context and switch back to the original one */
MemoryContextSwitchTo(oldcontext);
- MemoryContextReset(basic_archive_context);
+ MemoryContextReset((MemoryContext) arg);
/* Remove our exception handler */
PG_exception_stack = NULL;
@@ -211,7 +210,7 @@ basic_archive_file(const char *file, const char *path)
/* Reset our memory context and switch back to the original one */
MemoryContextSwitchTo(oldcontext);
- MemoryContextReset(basic_archive_context);
+ MemoryContextReset((MemoryContext) arg);
return true;
}
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 627c579f0e..939b5beb00 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,6 +99,7 @@ char *XLogArchiveLibrary = "";
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static void *arch_module_private = NULL;
/*
@@ -418,7 +419,7 @@ pgarch_ArchiverCopyLoop(void)
/* can't do anything if not configured ... */
if (ArchiveCallbacks->check_configured_cb != NULL &&
- !ArchiveCallbacks->check_configured_cb())
+ !ArchiveCallbacks->check_configured_cb(arch_module_private))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -519,7 +520,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks->archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(xlog, pathname, arch_module_private);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -853,7 +854,7 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- ArchiveCallbacks = (*archive_init) ();
+ ArchiveCallbacks = (*archive_init) (&arch_module_private);
if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
@@ -869,5 +870,5 @@ static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
if (ArchiveCallbacks->shutdown_cb != NULL)
- ArchiveCallbacks->shutdown_cb();
+ ArchiveCallbacks->shutdown_cb(arch_module_private);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index dde20c83de..31ceca67a3 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -22,9 +22,9 @@
#include "pgstat.h"
#include "postmaster/archive_module.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(void *arg);
+static bool shell_archive_file(const char *file, const char *path, void *arg);
+static void shell_archive_shutdown(void *arg);
static const ArchiveModuleCallbacks shell_archive_callbacks = {
.check_configured_cb = shell_archive_configured,
@@ -33,7 +33,7 @@ static const ArchiveModuleCallbacks shell_archive_callbacks = {
};
const ArchiveModuleCallbacks *
-shell_archive_init(void)
+shell_archive_init(void **arg)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
@@ -41,13 +41,13 @@ shell_archive_init(void)
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(void *arg)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(const char *file, const char *path, void *arg)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -129,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(void *arg)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index 67b5624874..fb4ef8730e 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -25,24 +25,24 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
*/
typedef struct ArchiveModuleCallbacks
{
- bool (*check_configured_cb) (void);
- bool (*archive_file_cb) (const char *file, const char *path);
- void (*shutdown_cb) (void);
+ bool (*check_configured_cb) (void *arg);
+ bool (*archive_file_cb) (const char *file, const char *path, void *arg);
+ void (*shutdown_cb) (void *arg);
} ArchiveModuleCallbacks;
/*
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void **arg);
-extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void **arg);
/*
* Since the logic for archiving via a shell command is in the core server
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern const ArchiveModuleCallbacks *shell_archive_init(void);
+extern const ArchiveModuleCallbacks *shell_archive_init(void **arg);
#endif /* _ARCHIVE_MODULE_H */
--
2.25.1
On Fri, Jan 27, 2023 at 10:27:29PM -0800, Nathan Bossart wrote:
Here is a work-in-progress patch set for adjusting the archive modules
interface. Is this roughly what you had in mind?
I have been catching up with what is happening here. I can get
behind the idea to use the term "callbacks" vs "context" for clarity,
to move the callback definitions into their own header, and to add
extra arguments to the callback functions for some private data.
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void **arg)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ (*arg) = (void *) AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+
+ return &basic_archive_callbacks;
Now, I find this part, where we use a double pointer to allow the
module initialization to create and give back a private area, rather
confusing, and I think that this could be bug-prone, as well. Once
you incorporate some data within the set of callbacks, isn't this
stuff close to a "state" data, or just something that we could call
only an "ArchiveModule"? Could it make more sense to have
_PG_archive_module_init return a structure with everything rather than
a separate in/out argument? Here is the idea, simply:
typedef struct ArchiveModule {
ArchiveCallbacks *routines;
void *private_data;
/* Potentially more here, like some flags? */
} ArchiveModule;
That would be closer to the style of xlogreader.h, for example.
All these choices should be documented in archive_module.h, at the
end.
--
Michael
On Mon, Jan 30, 2023 at 04:51:38PM +0900, Michael Paquier wrote:
Now, I find this part, where we use a double pointer to allow the
module initialization to create and give back a private area, rather
confusing, and I think that this could be bug-prone, as well. Once
you incorporate some data within the set of callbacks, isn't this
stuff close to a "state" data, or just something that we could call
only an "ArchiveModule"? Could it make more sense to have
_PG_archive_module_init return a structure with everything rather than
a separate in/out argument? Here is the idea, simply:
typedef struct ArchiveModule {
ArchiveCallbacks *routines;
void *private_data;
/* Potentially more here, like some flags? */
} ArchiveModule;
Yeah, we could probably invent an ArchiveModuleContext struct. I think
this is similar to how LogicalDecodingContext is used.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Hi,
On 2023-01-30 16:51:38 +0900, Michael Paquier wrote:
On Fri, Jan 27, 2023 at 10:27:29PM -0800, Nathan Bossart wrote:
Here is a work-in-progress patch set for adjusting the archive modules
interface. Is this roughly what you had in mind?I have been catching up with what is happening here. I can get
behind the idea to use the term "callbacks" vs "context" for clarity,
to move the callback definitions into their own header, and to add
extra arguments to the callback functions for some private data.-void -_PG_archive_module_init(ArchiveModuleCallbacks *cb) +const ArchiveModuleCallbacks * +_PG_archive_module_init(void **arg) { AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);- cb->check_configured_cb = basic_archive_configured; - cb->archive_file_cb = basic_archive_file; + (*arg) = (void *) AllocSetContextCreate(TopMemoryContext, + "basic_archive", + ALLOCSET_DEFAULT_SIZES); + + return &basic_archive_callbacks;
Now, I find this part, where we use a double pointer to allow the
module initialization to create and give back a private area, rather
confusing, and I think that this could be bug-prone, as well.
I don't think _PG_archive_module_init() should actually allocate a memory
context and do other similar initializations. Instead it should just return
'const ArchiveModuleCallbacks*', typically a single line.
Allocations etc should happen in one of the callbacks. That way we can
actually have multiple instances of a module.
Once
you incorporate some data within the set of callbacks, isn't this
stuff close to a "state" data, or just something that we could call
only an "ArchiveModule"? Could it make more sense to have
_PG_archive_module_init return a structure with everything rather than
a separate in/out argument? Here is the idea, simply:
typedef struct ArchiveModule {
ArchiveCallbacks *routines;
void *private_data;
/* Potentially more here, like some flags? */
} ArchiveModule;
I don't like this much. This still basically ends up with the module callbacks
not being sufficient to instantiate an archive module.
Greetings,
Andres Freund
On Mon, Jan 30, 2023 at 11:48:10AM -0800, Andres Freund wrote:
I don't think _PG_archive_module_init() should actually allocate a memory
context and do other similar initializations. Instead it should just return
'const ArchiveModuleCallbacks*', typically a single line.Allocations etc should happen in one of the callbacks. That way we can
actually have multiple instances of a module.
I think we'd need to invent a startup callback for archive modules for this
to work, but that's easy enough.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Mon, Jan 30, 2023 at 12:04:22PM -0800, Nathan Bossart wrote:
On Mon, Jan 30, 2023 at 11:48:10AM -0800, Andres Freund wrote:
I don't think _PG_archive_module_init() should actually allocate a memory
context and do other similar initializations. Instead it should just return
'const ArchiveModuleCallbacks*', typically a single line.Allocations etc should happen in one of the callbacks. That way we can
actually have multiple instances of a module.I think we'd need to invent a startup callback for archive modules for this
to work, but that's easy enough.
If you don't return some (void *) pointing to a private area that
would be stored by the backend, allocated as part of the loading path,
I agree that an extra callback is what makes the most sense,
presumably called around the beginning of PgArchiverMain(). Doing
this kind of one-time action in the file callback woud be weird..
--
Michael
On Tue, Jan 31, 2023 at 08:13:11AM +0900, Michael Paquier wrote:
On Mon, Jan 30, 2023 at 12:04:22PM -0800, Nathan Bossart wrote:
On Mon, Jan 30, 2023 at 11:48:10AM -0800, Andres Freund wrote:
I don't think _PG_archive_module_init() should actually allocate a memory
context and do other similar initializations. Instead it should just return
'const ArchiveModuleCallbacks*', typically a single line.Allocations etc should happen in one of the callbacks. That way we can
actually have multiple instances of a module.I think we'd need to invent a startup callback for archive modules for this
to work, but that's easy enough.If you don't return some (void *) pointing to a private area that
would be stored by the backend, allocated as part of the loading path,
I agree that an extra callback is what makes the most sense,
presumably called around the beginning of PgArchiverMain(). Doing
this kind of one-time action in the file callback woud be weird..
Okay, here is a new patch set with the aforementioned adjustments and
documentation updates.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v2-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From f07a2c65439ed1f1b38741f8796563e62adef6bb Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v2 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8ecdb9ca23..36800127e8 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -416,8 +416,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -518,7 +518,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -824,7 +824,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -837,7 +837,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -854,9 +854,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -869,6 +869,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v2-0002-move-archive-module-exports-to-dedicated-header.patchtext/x-diff; charset=us-asciiDownload
From ea03c90a99ed07a4df8537dfa4916d7858decef9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v2 2/3] move archive module exports to dedicated header
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 1 +
src/backend/postmaster/shell_archive.c | 2 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 54 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 ------------------
6 files changed, 58 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 36800127e8..eca02d5e74 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,6 +34,7 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
#include "storage/fd.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..b64297e3bb 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,7 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c5a95f5dcc..663c6e0011 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..099050c1ca
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,54 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
v2-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From d4693b401c7489284dec68de6182704de4ae390a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v2 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 42 +++++++++++++++++--------
doc/src/sgml/archive-modules.sgml | 30 +++++++++++++-----
src/backend/postmaster/pgarch.c | 22 +++++++------
src/backend/postmaster/shell_archive.c | 29 ++++++++++-------
src/include/postmaster/archive_module.h | 14 +++++----
5 files changed, 89 insertions(+), 48 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..2b7680947f 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -41,14 +41,21 @@
PG_MODULE_MAGIC;
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void *basic_archive_startup(void);
+static bool basic_archive_configured(void *private_state);
+static bool basic_archive_file(const char *file, const char *path, void *private_state);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +74,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +81,25 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void *
+basic_archive_startup(void)
+{
+ return (void *) AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -135,7 +150,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(void *private_state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +161,11 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(const char *file, const char *path, void *private_state)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ MemoryContext basic_archive_context = (MemoryContext) private_state;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..8c4a3d9365 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,18 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return a
+ struct filled with the callback function pointers for individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +73,22 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs a state, it should
+ return a pointer to the state. This pointer will be passed to each of the
+ module's other callbacks via the <literal>void *private_state</literal>
+ argument.
+
+<programlisting>
+typedef void *(*ArchiveStartupCB) (void);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +99,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (void *private_state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +121,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path, void *private_state);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +144,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (void *private_state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index eca02d5e74..421425d0dd 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -98,7 +98,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static void *private_state = NULL;
/*
@@ -417,8 +418,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(private_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -519,7 +520,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(xlog, pathname, private_state);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -838,8 +839,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -855,12 +854,15 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ private_state = ArchiveCallbacks->startup_cb();
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -870,6 +872,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(private_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index b64297e3bb..dbd633477a 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -22,28 +22,33 @@
#include "pgstat.h"
#include "postmaster/archive_module.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(void *private_state);
+static bool shell_archive_file(const char *file, const char *path, void *private_state);
+static void shell_archive_shutdown(void *private_state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(void *private_state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(const char *file, const char *path, void *private_state)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -125,7 +130,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(void *private_state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index 099050c1ca..b4ea4f5dcd 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -25,12 +25,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void *(*ArchiveStartupCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (void *private_state);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path, void *private_state);
+typedef void (*ArchiveShutdownCB) (void *private_state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,15 +42,15 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
/*
* Since the logic for archiving via a shell command is in the core server
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _ARCHIVE_MODULE_H */
--
2.25.1
On Tue, Jan 31, 2023 at 03:30:13PM -0800, Nathan Bossart wrote:
Okay, here is a new patch set with the aforementioned adjustments and
documentation updates.
So, it looks like you have addressed the feedback received here, as
of:
- Rename of Context to Callback.
- Move of the definition into their own header.
- Introduction of a callback for the startup initialization.
- Pass down a private state to each callback.
I have a few minor comments.
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
Storing that in archive_module.h is not incorrect, still feels a bit
unnatural. I would have used a separate header for clarity. It may
not sound like a big deal, but we may want this separation if
archive_module.h is used in some frontend code in the future. Perhaps
that will never be the case, but I've seen many fancy (as in useful)
proposals in the past when it comes to such things.
static bool
-shell_archive_configured(void)
+shell_archive_configured(void *private_state)
{
return XLogArchiveCommand[0] != '\0';
Maybe check that in this context private_state should be NULL? The
other two callbacks could use an assert, as well.
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return a
+ struct filled with the callback function pointers for individual actions.
Worth mentioning the name of the structure, as of "This function must
return a structure ArchiveModuleCallbacks filled with.."
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs a state, it should
+ return a pointer to the state. This pointer will be passed to each of the
+ module's other callbacks via the <literal>void *private_state</literal>
+ argument.
Not sure about the complexity of two sentences here. This could
simply be:
This function can return a pointer to an area of memory dedicated to
the state of the archive module loaded. This pointer is passed to
each of the module's other callbacks as the argument
<literal>private_state</literal>.
Side note: it looks like there is nothing in archive-modules.sgml
telling that these modules are only loaded by the archiver process.
--
Michael
Hi,
On 2023-01-31 15:30:13 -0800, Nathan Bossart wrote:
+/* + * basic_archive_startup + * + * Creates the module's memory context. + */ +void * +basic_archive_startup(void) +{ + return (void *) AllocSetContextCreate(TopMemoryContext, + "basic_archive", + ALLOCSET_DEFAULT_SIZES); }
I'd make basic_archive's private data a struct, with a member for the
context, but it's not that important.
I'd also be inclined to do the same for the private_state you're passing
around for each module. Even if it's just to reduce the number of
functions accepting void * - loosing compiler type checking isn't great.
So maybe an ArchiveModuleState { void *private_data } that's passed to
basic_archive_startup() and all the other callbacks.
Greetings,
Andres Freund
On Wed, Feb 01, 2023 at 03:54:26AM -0800, Andres Freund wrote:
I'd make basic_archive's private data a struct, with a member for the
context, but it's not that important.I'd also be inclined to do the same for the private_state you're passing
around for each module. Even if it's just to reduce the number of
functions accepting void * - loosing compiler type checking isn't great.So maybe an ArchiveModuleState { void *private_data } that's passed to
basic_archive_startup() and all the other callbacks.
Here's a new patch set in which I've attempted to address this feedback and
Michael's feedback.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v3-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From 0bcfb2dc8ef1544dcd6a8cc07a784b36a38febad Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v3 2/3] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 22 ++++++++++++
7 files changed, 75 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index dda6698509..ec47b2cc20 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..35f88c1222 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c5a95f5dcc..663c6e0011 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..f6943c0aba
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v3-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 2ad23986bfe0123aed43350a0428d2e07459b00e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v3 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 51 ++++++++++++++++++-------
doc/src/sgml/archive-modules.sgml | 33 +++++++++++++---
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 112 insertions(+), 47 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..f36a1def15 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(const char *file, const char *path, ArchiveModuleState *state);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +86,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = palloc0(sizeof(BasicArchiveData));
+
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -135,7 +158,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +169,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(const char *file, const char *path, ArchiveModuleState *state)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..cc8a834a2e 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
+ <function>_PG_archive_module_init</function>. This function must return an
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <literal>state->private_data</literal> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path, ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index ec47b2cc20..253d456837 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(xlog, pathname, archive_module_state);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 35f88c1222..5eda7671e8 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,28 +23,33 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(const char *file, const char *path, ArchiveModuleState *state);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(const char *file, const char *path, ArchiveModuleState *state)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -126,7 +131,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..d07dd26c6e 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path, ArchiveModuleState *state);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index f6943c0aba..66b2fda401 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -17,6 +17,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v3-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From e54ed061b772e7f0859a37612ed1effebc5c6ba1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v3 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 3c714a79c6..dda6698509 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
Hi,
On 2023-02-01 12:15:29 -0800, Nathan Bossart wrote:
Here's a new patch set in which I've attempted to address this feedback and
Michael's feedback.
Looks better!
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary; * For more information about the purpose of each callback, refer to the * archive modules documentation. */ -typedef bool (*ArchiveCheckConfiguredCB) (void); -typedef bool (*ArchiveFileCB) (const char *file, const char *path); -typedef void (*ArchiveShutdownCB) (void); +typedef void (*ArchiveStartupCB) (ArchiveModuleState *state); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); +typedef bool (*ArchiveFileCB) (const char *file, const char *path, ArchiveModuleState *state); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
Personally I'd always pass ArchiveModuleState *state as the first arg,
but it's not important.
Greetings,
Andres Freund
On Wed, Feb 01, 2023 at 01:06:06PM -0800, Andres Freund wrote:
On 2023-02-01 12:15:29 -0800, Nathan Bossart wrote:
Here's a new patch set in which I've attempted to address this feedback and
Michael's feedback.Looks better!
Thanks!
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary; * For more information about the purpose of each callback, refer to the * archive modules documentation. */ -typedef bool (*ArchiveCheckConfiguredCB) (void); -typedef bool (*ArchiveFileCB) (const char *file, const char *path); -typedef void (*ArchiveShutdownCB) (void); +typedef void (*ArchiveStartupCB) (ArchiveModuleState *state); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); +typedef bool (*ArchiveFileCB) (const char *file, const char *path, ArchiveModuleState *state); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);Personally I'd always pass ArchiveModuleState *state as the first arg,
but it's not important.
Yeah, that's nicer. cfbot is complaining about a missing #include, so I
need to send a new revision anyway.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v4-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From e54ed061b772e7f0859a37612ed1effebc5c6ba1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v4 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 3c714a79c6..dda6698509 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v4-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From 37e19c986b548f5d281190e196e01ce94f8c4391 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v4 2/3] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index dda6698509..ec47b2cc20 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..35f88c1222 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c5a95f5dcc..663c6e0011 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v4-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 4efe9de26a6ab1a1b606f723cc0ef7422bff0767 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v4 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 51 ++++++++++++++++++-------
doc/src/sgml/archive-modules.sgml | 33 +++++++++++++---
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 112 insertions(+), 47 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..3b4373b562 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +86,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = palloc0(sizeof(BasicArchiveData));
+
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -135,7 +158,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +169,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2ec5d12d97 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
+ <function>_PG_archive_module_init</function>. This function must return an
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <literal>state->private_data</literal> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index ec47b2cc20..5931d5ffa0 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 35f88c1222..cc2585218d 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,28 +23,33 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -126,7 +131,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
On Wed, Feb 01, 2023 at 01:23:26PM -0800, Nathan Bossart wrote:
Yeah, that's nicer. cfbot is complaining about a missing #include, so I
need to send a new revision anyway.
Okay, the changes done here look straight-forward seen from here. I
got one small-ish comment.
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = palloc0(sizeof(BasicArchiveData));
Perhaps this should use MemoryContextAlloc() rather than a plain
palloc(). This should not matter based on the position where the
startup callback is called, still that may be a pattern worth
encouraging.
--
Michael
On Thu, Feb 02, 2023 at 02:34:17PM +0900, Michael Paquier wrote:
Okay, the changes done here look straight-forward seen from here. I
got one small-ish comment.+basic_archive_startup(ArchiveModuleState *state) +{ + BasicArchiveData *data = palloc0(sizeof(BasicArchiveData));Perhaps this should use MemoryContextAlloc() rather than a plain
palloc(). This should not matter based on the position where the
startup callback is called, still that may be a pattern worth
encouraging.
Good call.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v5-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From 97585c26468a39b0f881a9cc8b81ea614da9a547 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v5 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 3c714a79c6..dda6698509 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v5-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From 8f26acafe541abcdd9261c2668876ff288fc02f4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v5 2/3] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index dda6698509..ec47b2cc20 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..35f88c1222 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v5-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 898d7dd7d534fc60c22194c2bdc2d2ac7bfa43f5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v5 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 53 +++++++++++++++++++------
doc/src/sgml/archive-modules.sgml | 33 ++++++++++++---
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 114 insertions(+), 47 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..74a51b9288 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +86,30 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -135,7 +160,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +171,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2ec5d12d97 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
+ <function>_PG_archive_module_init</function>. This function must return an
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <literal>state->private_data</literal> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index ec47b2cc20..5931d5ffa0 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 35f88c1222..cc2585218d 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,28 +23,33 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -126,7 +131,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
On Thu, Feb 02, 2023 at 11:37:00AM -0800, Nathan Bossart wrote:
On Thu, Feb 02, 2023 at 02:34:17PM +0900, Michael Paquier wrote:
Okay, the changes done here look straight-forward seen from here. I
got one small-ish comment.+basic_archive_startup(ArchiveModuleState *state) +{ + BasicArchiveData *data = palloc0(sizeof(BasicArchiveData));Perhaps this should use MemoryContextAlloc() rather than a plain
palloc(). This should not matter based on the position where the
startup callback is called, still that may be a pattern worth
encouraging.Good call.
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
This needs a structname markup.
+ can use <literal>state->private_data</literal> to store it.
And here it would be structfield.
As far as I can see, all the points raised about this redesign seem to
have been addressed. Andres, any comments?
--
Michael
On Sat, Feb 04, 2023 at 11:59:20AM +0900, Michael Paquier wrote:
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
This needs a structname markup.+ can use <literal>state->private_data</literal> to store it.
And here it would be structfield.
fixed
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v6-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From a0241aca9957b2edb0db151bc9d7f9a1505b207c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v6 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..065d7d1313 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v6-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From 3c0b4c7a55bf93a2e6a9da0ec33f67705cb75574 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v6 2/3] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 065d7d1313..281d9fd8b7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..35f88c1222 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v6-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From da7af14b583290c9a70e09077fd8df53476a88ad Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v6 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 53 +++++++++++++++++++------
doc/src/sgml/archive-modules.sgml | 35 ++++++++++++----
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 115 insertions(+), 48 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..74a51b9288 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +86,30 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -135,7 +160,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +171,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2db1b19216 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return an
+ <structname>ArchiveModuleCallbacks</structname> struct filled with the
+ callback function pointers for individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 281d9fd8b7..99a07996f7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 35f88c1222..cc2585218d 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,28 +23,33 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -126,7 +131,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
On Sat, Feb 04, 2023 at 10:14:36AM -0800, Nathan Bossart wrote:
On Sat, Feb 04, 2023 at 11:59:20AM +0900, Michael Paquier wrote:
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
This needs a structname markup.+ can use <literal>state->private_data</literal> to store it.
And here it would be structfield.fixed
Andres, did you have the change to look at that? I did look at it,
but it may not address all the points you may have in mind.
--
Michael
Hi,
On 2023-02-08 16:23:34 +0900, Michael Paquier wrote:
On Sat, Feb 04, 2023 at 10:14:36AM -0800, Nathan Bossart wrote:
On Sat, Feb 04, 2023 at 11:59:20AM +0900, Michael Paquier wrote:
+ ArchiveModuleCallbacks struct filled with the callback function pointers for
This needs a structname markup.+ can use <literal>state->private_data</literal> to store it.
And here it would be structfield.fixed
Andres, did you have the change to look at that? I did look at it,
but it may not address all the points you may have in mind.
Yes, I think this looks pretty good now.
One minor thing: I don't think we really need the AssertVariableIsOfType() for
anything but the Init() one?
Greetings,
Andres Freund
On Wed, Feb 08, 2023 at 08:27:13AM -0800, Andres Freund wrote:
One minor thing: I don't think we really need the AssertVariableIsOfType() for
anything but the Init() one?
This is another part that was borrowed from logical decoding output
plugins. I'm not sure this adds much since f2b73c8 ("Add central
declarations for dlsym()ed symbols"). Perhaps we should remove all of
these assertions for functions that now have central declarations.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Hi,
On 2023-02-08 09:27:05 -0800, Nathan Bossart wrote:
On Wed, Feb 08, 2023 at 08:27:13AM -0800, Andres Freund wrote:
One minor thing: I don't think we really need the AssertVariableIsOfType() for
anything but the Init() one?This is another part that was borrowed from logical decoding output
plugins.
I know :(. It was needed in an earlier version of the output plugin interface,
where all the different callbacks were looked up via dlsym(), but should have
been removed after that.
I'm not sure this adds much since f2b73c8 ("Add central
declarations for dlsym()ed symbols"). Perhaps we should remove all of
these assertions for functions that now have central declarations.
Most of them weren't needed even before that.
And yes, I'd be for a patch to remove all of those assertions.
Greetings,
Andres Freund
On Wed, Feb 08, 2023 at 09:33:44AM -0800, Andres Freund wrote:
And yes, I'd be for a patch to remove all of those assertions.
done
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v7-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From 09fcca03d4a91be5757201cc311d3dad08463cd0 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v7 1/4] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..065d7d1313 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v7-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From 870999a50d3372d9ed33583b4d6eac05a2accada Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v7 2/4] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..87bbb2174d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 065d7d1313..281d9fd8b7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..35f88c1222 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v7-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 3bbc2a73e78a1438848817fd64e588b437c0c6ba Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v7 3/4] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 53 +++++++++++++++++++------
doc/src/sgml/archive-modules.sgml | 35 ++++++++++++----
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 115 insertions(+), 48 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 87bbb2174d..74a51b9288 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,13 +86,30 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
-void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
{
AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
+void
+basic_archive_startup(ArchiveModuleState *state)
+{
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -135,7 +160,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -146,10 +171,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2db1b19216 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return an
+ <structname>ArchiveModuleCallbacks</structname> struct filled with the
+ callback function pointers for individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 281d9fd8b7..99a07996f7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 35f88c1222..cc2585218d 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,28 +23,33 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -126,7 +131,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v7-0004-remove-unnecessary-assertions-for-functions-with-.patchtext/x-diff; charset=us-asciiDownload
From 63e36dc26d4055d77a0efd57c2f8a0c5ea9db90a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 8 Feb 2023 09:51:46 -0800
Subject: [PATCH v7 4/4] remove unnecessary assertions for functions with
central declarations
---
contrib/basic_archive/basic_archive.c | 2 --
contrib/test_decoding/test_decoding.c | 2 --
src/backend/postmaster/shell_archive.c | 2 --
src/backend/replication/pgoutput/pgoutput.c | 2 --
4 files changed, 8 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 74a51b9288..9afa03c5e2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -89,8 +89,6 @@ _PG_init(void)
const ArchiveModuleCallbacks *
_PG_archive_module_init(void)
{
- AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
-
return &basic_archive_callbacks;
}
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index e523d22eba..b7e6048647 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -127,8 +127,6 @@ _PG_init(void)
void
_PG_output_plugin_init(OutputPluginCallbacks *cb)
{
- AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
-
cb->startup_cb = pg_decode_startup;
cb->begin_cb = pg_decode_begin_txn;
cb->change_cb = pg_decode_change;
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index cc2585218d..54b02ca152 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -37,8 +37,6 @@ static const ArchiveModuleCallbacks shell_archive_callbacks = {
const ArchiveModuleCallbacks *
shell_archive_init(void)
{
- AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
-
return &shell_archive_callbacks;
}
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 73b080060d..98377c094b 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -248,8 +248,6 @@ static void pgoutput_column_list_init(PGOutputData *data,
void
_PG_output_plugin_init(OutputPluginCallbacks *cb)
{
- AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
-
cb->startup_cb = pgoutput_startup;
cb->begin_cb = pgoutput_begin_txn;
cb->change_cb = pgoutput_change;
--
2.25.1
Hi,
On 2023-02-08 09:57:56 -0800, Nathan Bossart wrote:
On Wed, Feb 08, 2023 at 09:33:44AM -0800, Andres Freund wrote:
And yes, I'd be for a patch to remove all of those assertions.
done
If you'd reorder it so that 0004 applies independently from the other changes,
I'd just push that now.
I was remembering additional AssertVariableIsOfType(), but it looks like we
actually did remember to take them out when redesigning the output plugin
interface...
Greetings,
Andres Freund
On Wed, Feb 08, 2023 at 10:24:18AM -0800, Andres Freund wrote:
If you'd reorder it so that 0004 applies independently from the other changes,
I'd just push that now.
done
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v8-0003-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From bf2b5007a4ad105d683f58e35d75d0e86666d293 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v8 3/4] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..384336884d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 065d7d1313..281d9fd8b7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 7771b951b7..0f0558e155 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v8-0004-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 6a143527ee2ad7f1a3aebc3564da20cab21a0d5e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v8 4/4] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 51 +++++++++++++++++++------
doc/src/sgml/archive-modules.sgml | 35 +++++++++++++----
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 114 insertions(+), 47 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 384336884d..9afa03c5e2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +86,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +158,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +169,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2db1b19216 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return an
+ <structname>ArchiveModuleCallbacks</structname> struct filled with the
+ callback function pointers for individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 281d9fd8b7..99a07996f7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 0f0558e155..54b02ca152 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,26 +23,31 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -124,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v8-0001-remove-unnecessary-assertions-for-functions-with-.patchtext/x-diff; charset=us-asciiDownload
From 688566e23519f173a805696e4f00345f30d5d8e6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 8 Feb 2023 09:51:46 -0800
Subject: [PATCH v8 1/4] remove unnecessary assertions for functions with
central declarations
---
contrib/basic_archive/basic_archive.c | 2 --
contrib/test_decoding/test_decoding.c | 2 --
src/backend/postmaster/shell_archive.c | 2 --
src/backend/replication/pgoutput/pgoutput.c | 2 --
4 files changed, 8 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 3d29711a31..36b7a4814a 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -81,8 +81,6 @@ _PG_init(void)
void
_PG_archive_module_init(ArchiveModuleCallbacks *cb)
{
- AssertVariableIsOfType(&_PG_archive_module_init, ArchiveModuleInit);
-
cb->check_configured_cb = basic_archive_configured;
cb->archive_file_cb = basic_archive_file;
}
diff --git a/contrib/test_decoding/test_decoding.c b/contrib/test_decoding/test_decoding.c
index e523d22eba..b7e6048647 100644
--- a/contrib/test_decoding/test_decoding.c
+++ b/contrib/test_decoding/test_decoding.c
@@ -127,8 +127,6 @@ _PG_init(void)
void
_PG_output_plugin_init(OutputPluginCallbacks *cb)
{
- AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
-
cb->startup_cb = pg_decode_startup;
cb->begin_cb = pg_decode_begin_txn;
cb->change_cb = pg_decode_change;
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 806b81c3f2..7771b951b7 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -29,8 +29,6 @@ static void shell_archive_shutdown(void);
void
shell_archive_init(ArchiveModuleCallbacks *cb)
{
- AssertVariableIsOfType(&shell_archive_init, ArchiveModuleInit);
-
cb->check_configured_cb = shell_archive_configured;
cb->archive_file_cb = shell_archive_file;
cb->shutdown_cb = shell_archive_shutdown;
diff --git a/src/backend/replication/pgoutput/pgoutput.c b/src/backend/replication/pgoutput/pgoutput.c
index 73b080060d..98377c094b 100644
--- a/src/backend/replication/pgoutput/pgoutput.c
+++ b/src/backend/replication/pgoutput/pgoutput.c
@@ -248,8 +248,6 @@ static void pgoutput_column_list_init(PGOutputData *data,
void
_PG_output_plugin_init(OutputPluginCallbacks *cb)
{
- AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
-
cb->startup_cb = pgoutput_startup;
cb->begin_cb = pgoutput_begin_txn;
cb->change_cb = pgoutput_change;
--
2.25.1
v8-0002-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From c09374708bdde1eab98fdcad320d5689c6764bc2 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v8 2/4] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..065d7d1313 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
On Wed, Feb 08, 2023 at 09:16:19PM -0800, Andres Freund wrote:
Pushed. Thanks!
Thanks!
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
rebased for cfbot
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v9-0001-s-ArchiveContext-ArchiveCallbacks.patchtext/x-diff; charset=us-asciiDownload
From ea6339276c6863f260572dfc816f9dd27ac7b516 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:01:22 -0800
Subject: [PATCH v9 1/3] s/ArchiveContext/ArchiveCallbacks
---
src/backend/postmaster/pgarch.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..065d7d1313 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -97,7 +97,7 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static ArchiveModuleCallbacks ArchiveCallbacks;
/*
@@ -406,8 +406,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks.check_configured_cb != NULL &&
+ !ArchiveCallbacks.check_configured_cb())
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +508,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +814,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,7 +827,7 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
+ memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
/*
* If shell archiving is enabled, use our special initialization function.
@@ -844,9 +844,9 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ (*archive_init) (&ArchiveCallbacks);
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks.archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
@@ -859,6 +859,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks.shutdown_cb != NULL)
+ ArchiveCallbacks.shutdown_cb();
}
--
2.25.1
v9-0002-move-archive-module-exports-to-dedicated-headers.patchtext/x-diff; charset=us-asciiDownload
From e4e28ed2aea395e5120389e92d6944c48468208e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Fri, 27 Jan 2023 21:16:34 -0800
Subject: [PATCH v9 2/3] move archive module exports to dedicated headers
---
contrib/basic_archive/basic_archive.c | 2 +-
src/backend/postmaster/pgarch.c | 2 ++
src/backend/postmaster/shell_archive.c | 3 +-
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 47 +++++++++++++++++++++++++
src/include/postmaster/pgarch.h | 39 --------------------
src/include/postmaster/shell_archive.h | 24 +++++++++++++
7 files changed, 77 insertions(+), 41 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..384336884d 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 065d7d1313..281d9fd8b7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 7771b951b7..0f0558e155 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,7 +20,8 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
static bool shell_archive_configured(void);
static bool shell_archive_file(const char *file, const char *path);
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..f5015ff95d
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (void);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+
+extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..3cf737ce8e
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
v9-0003-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From d594c11bcb2bf12ca21f3a6f87f15398932d2ae7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 31 Jan 2023 14:58:40 -0800
Subject: [PATCH v9 3/3] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 51 +++++++++++++++++++------
doc/src/sgml/archive-modules.sgml | 35 +++++++++++++----
src/backend/postmaster/pgarch.c | 23 ++++++-----
src/backend/postmaster/shell_archive.c | 29 ++++++++------
src/include/postmaster/archive_module.h | 21 +++++++---
src/include/postmaster/shell_archive.h | 2 +-
6 files changed, 114 insertions(+), 47 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 384336884d..9afa03c5e2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +86,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +158,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +169,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..2db1b19216 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,23 +47,30 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. This function must return an
+ <structname>ArchiveModuleCallbacks</structname> struct filled with the
+ callback function pointers for individual actions.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
others are optional.
</para>
+
+ <note>
+ <para>
+ <varname>archive_library</varname> is only loaded in the archiver process.
+ </para>
+ </note>
</sect1>
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module needs to have a state, it
+ can use <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +126,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 281d9fd8b7..99a07996f7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -99,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveCallbacks;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -408,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveCallbacks.check_configured_cb != NULL &&
- !ArchiveCallbacks.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -510,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveCallbacks.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -829,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveCallbacks, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -846,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveCallbacks);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveCallbacks.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -861,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveCallbacks.shutdown_cb != NULL)
- ArchiveCallbacks.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 0f0558e155..54b02ca152 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -23,26 +23,31 @@
#include "postmaster/archive_module.h"
#include "postmaster/shell_archive.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -124,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
index f5015ff95d..79e9d53534 100644
--- a/src/include/postmaster/archive_module.h
+++ b/src/include/postmaster/archive_module.h
@@ -17,6 +17,15 @@
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
/*
* Archive module callbacks
*
@@ -25,12 +34,14 @@ extern PGDLLIMPORT char *XLogArchiveLibrary;
* For more information about the purpose of each callback, refer to the
* archive modules documentation.
*/
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
@@ -40,8 +51,8 @@ typedef struct ArchiveModuleCallbacks
* Type of the shared library symbol _PG_archive_module_init that is looked
* up when loading an archive library.
*/
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
index 3cf737ce8e..be83c7715a 100644
--- a/src/include/postmaster/shell_archive.h
+++ b/src/include/postmaster/shell_archive.h
@@ -19,6 +19,6 @@
* and does not need to be loaded via a shared library, it has a special
* initialization function.
*/
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
Hi,
On 2023-02-09 11:39:17 -0800, Nathan Bossart wrote:
rebased for cfbot
I think this nearly ready. Michael, are you planning to commit this?
Personally I'd probably squash these into a single commit.
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml index ef02051f7f..2db1b19216 100644 --- a/doc/src/sgml/archive-modules.sgml +++ b/doc/src/sgml/archive-modules.sgml @@ -47,23 +47,30 @@ normal library search path is used to locate the library. To provide the required archive module callbacks and to indicate that the library is actually an archive module, it needs to provide a function named - <function>_PG_archive_module_init</function>. This function is passed a - struct that needs to be filled with the callback function pointers for - individual actions. + <function>_PG_archive_module_init</function>. This function must return an + <structname>ArchiveModuleCallbacks</structname> struct filled with the + callback function pointers for individual actions.
I'd probably mention that this should typically be of server lifetime / a
'static const' struct. Tableam documents this as follows:
The result of the function must be a pointer to a struct of type
<structname>TableAmRoutine</structname>, which contains everything that the
core code needs to know to make use of the table access method. The return
value needs to be of server lifetime, which is typically achieved by
defining it as a <literal>static const</literal> variable in global
scope
+ + <note> + <para> + <varname>archive_library</varname> is only loaded in the archiver process. + </para> + </note> </sect1>
That's not really related to any of the changes here, right?
I'm not sure it's a good idea to document that. We e.g. probably should allow
the library to check that the configuration is correct, at postmaster start,
rather than later, at runtime.
<sect1 id="archive-module-callbacks">
@@ -73,6 +80,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>+ <sect2 id="archive-module-startup"> + <title>Startup Callback</title> + <para> + The <function>startup_cb</function> callback is called shortly after the + module is loaded. This callback can be used to perform any additional + initialization required. If the archive module needs to have a state, it + can use <structfield>state->private_data</structfield> to store it.
s/needs to have a state/has state/?
@@ -83,7 +104,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.<programlisting> -typedef bool (*ArchiveCheckConfiguredCB) (void); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); </programlisting>If <literal>true</literal> is returned, the server will proceed with
Hm. I wonder if ArchiveCheckConfiguredCB() should actually work without the
state. We're not really doing anything yet, at that point, so it shouldn't
really need state?
The reason I'm wondering is that I think we should consider calling this from
the GUC assignment hook, at least in postmaster. Which would make it more
convenient to not have state, I think?
@@ -128,7 +149,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.<programlisting> -typedef void (*ArchiveShutdownCB) (void); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); </programlisting> </para> </sect2>
Perhaps mention that this needs to free state it allocated in the
ArchiveModuleState, or it'll be leaked?
Greetings,
Andres Freund
On Thu, Feb 09, 2023 at 12:18:55PM -0800, Andres Freund wrote:
On 2023-02-09 11:39:17 -0800, Nathan Bossart wrote:
Personally I'd probably squash these into a single commit.
done
I'd probably mention that this should typically be of server lifetime / a
'static const' struct. Tableam documents this as follows:
done
+ <note> + <para> + <varname>archive_library</varname> is only loaded in the archiver process. + </para> + </note> </sect1>That's not really related to any of the changes here, right?
I'm not sure it's a good idea to document that. We e.g. probably should allow
the library to check that the configuration is correct, at postmaster start,
rather than later, at runtime.
removed
+ <sect2 id="archive-module-startup"> + <title>Startup Callback</title> + <para> + The <function>startup_cb</function> callback is called shortly after the + module is loaded. This callback can be used to perform any additional + initialization required. If the archive module needs to have a state, it + can use <structfield>state->private_data</structfield> to store it.s/needs to have a state/has state/?
done
<programlisting> -typedef bool (*ArchiveCheckConfiguredCB) (void); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); </programlisting>If <literal>true</literal> is returned, the server will proceed with
Hm. I wonder if ArchiveCheckConfiguredCB() should actually work without the
state. We're not really doing anything yet, at that point, so it shouldn't
really need state?The reason I'm wondering is that I think we should consider calling this from
the GUC assignment hook, at least in postmaster. Which would make it more
convenient to not have state, I think?
I agree that this callback should typically not need the state, but I'm not
sure whether it fits into the assignment hook for archive_library. This
callback is primarily meant for situations when you have archiving enabled,
but your module isn't configured yet (e.g., archive_command is empty). In
this case, we keep the WAL around, but we don't try to archive it until
this hook returns true. It's up to each module to define that criteria. I
can imagine someone introducing a GUC in their archive module that
temporarily halts archiving via this callback. In that case, calling it
via a GUC assignment hook probably won't work. In fact, checking whether
archive_command is empty in that hook might not work either.
<programlisting> -typedef void (*ArchiveShutdownCB) (void); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); </programlisting> </para> </sect2>Perhaps mention that this needs to free state it allocated in the
ArchiveModuleState, or it'll be leaked?
done
I left this out originally because the archiver exits shortly after calling
this. However, if you have DSM segments or something, it's probably wise
to make sure those are cleaned up. And I suppose we might not always exit
immediately after this callback, so establishing the habit of freeing the
state could be a good idea. In addition to updating this part of the docs,
I wrote a shutdown callback for basic_archive that frees its state.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v10-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From fab6305c344deb83c5b5c30ca2c09dbe1925a36c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v10 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 91 +++++++++++++++++++++----
doc/src/sgml/archive-modules.sgml | 35 +++++++---
src/backend/postmaster/pgarch.c | 27 +++++---
src/backend/postmaster/shell_archive.c | 34 +++++----
src/backend/utils/misc/guc_tables.c | 1 +
src/include/postmaster/archive_module.h | 58 ++++++++++++++++
src/include/postmaster/pgarch.h | 39 -----------
src/include/postmaster/shell_archive.h | 24 +++++++
8 files changed, 224 insertions(+), 85 deletions(-)
create mode 100644 src/include/postmaster/archive_module.h
create mode 100644 src/include/postmaster/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..e4742c9c94 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -32,7 +32,7 @@
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
+#include "postmaster/archive_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,14 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static void basic_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = basic_archive_shutdown
+};
/*
* _PG_init
@@ -67,10 +80,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +87,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +159,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +170,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
@@ -366,3 +394,40 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_archive_shutdown
+ *
+ * Frees our allocated state.
+ */
+static void
+basic_archive_shutdown(ArchiveModuleState *state)
+{
+ BasicArchiveData *data;
+ MemoryContext basic_archive_context;
+
+ /*
+ * If we didn't get to storing the pointer to our allocated state, we don't
+ * have anything to clean up.
+ */
+ data = (BasicArchiveData *) (state->private_data);
+ if (data == NULL)
+ return;
+
+ /*
+ * If called within basic_archive_context, switch out of it before we
+ * delete it.
+ */
+ basic_archive_context = data->context;
+ if (CurrentMemoryContext == basic_archive_context)
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
+
+ /*
+ * Finally, free the state.
+ */
+ pfree(data);
+ state->private_data = NULL;
+}
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..668eb34991 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -125,10 +143,11 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
process exits (e.g., after an error) or the value of
<xref linkend="guc-archive-library"/> changes. If no
<function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..99a07996f7 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,8 +34,10 @@
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/interrupt.h"
#include "postmaster/pgarch.h"
+#include "postmaster/shell_archive.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c
index 7771b951b7..54b02ca152 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/postmaster/shell_archive.c
@@ -20,28 +20,34 @@
#include "access/xlog.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
-
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+#include "postmaster/archive_module.h"
+#include "postmaster/shell_archive.h"
+
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..c3ee83b691 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -52,6 +52,7 @@
#include "parser/parse_expr.h"
#include "parser/parser.h"
#include "pgstat.h"
+#include "postmaster/archive_module.h"
#include "postmaster/autovacuum.h"
#include "postmaster/bgworker_internals.h"
#include "postmaster/bgwriter.h"
diff --git a/src/include/postmaster/archive_module.h b/src/include/postmaster/archive_module.h
new file mode 100644
index 0000000000..79e9d53534
--- /dev/null
+++ b/src/include/postmaster/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
diff --git a/src/include/postmaster/shell_archive.h b/src/include/postmaster/shell_archive.h
new file mode 100644
index 0000000000..be83c7715a
--- /dev/null
+++ b/src/include/postmaster/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/postmaster/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "postmaster/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
--
2.25.1
On Thu, Feb 09, 2023 at 12:18:55PM -0800, Andres Freund wrote:
I think this nearly ready. Michael, are you planning to commit this?
I could take a stab at it, now if you feel strongly about doing it
yourselfof course feel free :)
Personally I'd probably squash these into a single commit.
Same impression here. Agreed that all these had better be merged
together, still keeping them separated made their review so much
easier.
--
Michael
On Thu, Feb 09, 2023 at 02:48:26PM -0800, Nathan Bossart wrote:
On Thu, Feb 09, 2023 at 12:18:55PM -0800, Andres Freund wrote:
<programlisting> -typedef bool (*ArchiveCheckConfiguredCB) (void); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); </programlisting>If <literal>true</literal> is returned, the server will proceed with
Hm. I wonder if ArchiveCheckConfiguredCB() should actually work without the
state. We're not really doing anything yet, at that point, so it shouldn't
really need state?The reason I'm wondering is that I think we should consider calling this from
the GUC assignment hook, at least in postmaster. Which would make it more
convenient to not have state, I think?I agree that this callback should typically not need the state, but I'm not
sure whether it fits into the assignment hook for archive_library. This
callback is primarily meant for situations when you have archiving enabled,
but your module isn't configured yet (e.g., archive_command is empty). In
this case, we keep the WAL around, but we don't try to archive it until
this hook returns true. It's up to each module to define that criteria. I
can imagine someone introducing a GUC in their archive module that
temporarily halts archiving via this callback. In that case, calling it
via a GUC assignment hook probably won't work. In fact, checking whether
archive_command is empty in that hook might not work either.
Keeping the state in the configure check callback does not strike me
as a problem, FWIW.
<programlisting> -typedef void (*ArchiveShutdownCB) (void); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); </programlisting> </para> </sect2>Perhaps mention that this needs to free state it allocated in the
ArchiveModuleState, or it'll be leaked?done
I left this out originally because the archiver exits shortly after calling
this. However, if you have DSM segments or something, it's probably wise
to make sure those are cleaned up. And I suppose we might not always exit
immediately after this callback, so establishing the habit of freeing the
state could be a good idea. In addition to updating this part of the docs,
I wrote a shutdown callback for basic_archive that frees its state.
This makes sense to me. Still, DSM segments had better be cleaned up
with dsm_backend_shutdown().
+ basic_archive_context = data->context;
+ if (CurrentMemoryContext == basic_archive_context)
+ MemoryContextSwitchTo(TopMemoryContext);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
This is a bit confusing, because it means that we enter in the
shutdown callback with one context, but exit it under
TopMemoryContext. Are you sure that this will be OK when there could
be multiple callbacks piled up with before_shmem_exit()? shmem_exit()
has nothing specific to memory contexts.
Is putting the new headers in src/include/postmaster/ the best move in
the long term? Perhaps that's fine, but I was wondering whether a new
location like archive/ would make more sense. pg_arch.h being in the
postmaster section is fine.
--
Michael
On Mon, Feb 13, 2023 at 04:37:10PM +0900, Michael Paquier wrote:
+ basic_archive_context = data->context; + if (CurrentMemoryContext == basic_archive_context) + MemoryContextSwitchTo(TopMemoryContext); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context);This is a bit confusing, because it means that we enter in the
shutdown callback with one context, but exit it under
TopMemoryContext. Are you sure that this will be OK when there could
be multiple callbacks piled up with before_shmem_exit()? shmem_exit()
has nothing specific to memory contexts.
Well, we can't free the memory context while we are in it, so we have to
switch to another one. I agree that this is a bit confusing, though.
On second thought, I'm not sure it's important to make sure the state is
freed in the shutdown callback. It's only called just before the archiver
process exits, so we're not really at risk of leaking anything. I suppose
we might not always restart the archiver in this case, but I also don't
anticipate that behavior changing in the near future. I think this
callback is more useful for things like shutting down background workers.
I went ahead and removed the shutdown callback from basic_archive and the
note about leaking from the documentation.
Is putting the new headers in src/include/postmaster/ the best move in
the long term? Perhaps that's fine, but I was wondering whether a new
location like archive/ would make more sense. pg_arch.h being in the
postmaster section is fine.
I moved them to archive/.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v11-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 2d4231d40b47a01f46dc2d151661eba46b8ba3b6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v11 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 53 ++++++++++++-----
doc/src/sgml/archive-modules.sgml | 32 +++++++---
src/backend/Makefile | 2 +-
src/backend/archive/Makefile | 18 ++++++
src/backend/archive/meson.build | 5 ++
.../{postmaster => archive}/shell_archive.c | 30 ++++++----
src/backend/meson.build | 1 +
src/backend/postmaster/Makefile | 1 -
src/backend/postmaster/meson.build | 1 -
src/backend/postmaster/pgarch.c | 27 +++++----
src/backend/utils/misc/guc_tables.c | 1 +
src/include/archive/archive_module.h | 58 +++++++++++++++++++
src/include/archive/shell_archive.h | 22 +++++++
src/include/postmaster/pgarch.h | 39 -------------
14 files changed, 205 insertions(+), 85 deletions(-)
create mode 100644 src/backend/archive/Makefile
create mode 100644 src/backend/archive/meson.build
rename src/backend/{postmaster => archive}/shell_archive.c (78%)
create mode 100644 src/include/archive/archive_module.h
create mode 100644 src/include/archive/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..8a6bc25db5 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -30,9 +30,9 @@
#include <sys/time.h>
#include <unistd.h>
+#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,15 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -67,10 +79,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +86,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +158,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +169,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..8ea697a5bc 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -128,7 +146,7 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
these situations.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 86e6dcb792..e4bf0fe9c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -17,7 +17,7 @@ subdir = src/backend
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = access backup bootstrap catalog parser commands executor \
+SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile
new file mode 100644
index 0000000000..8d2860d0df
--- /dev/null
+++ b/src/backend/archive/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/archive
+#
+# IDENTIFICATION
+# src/backend/archive/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/archive
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_archive.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build
new file mode 100644
index 0000000000..e194282931
--- /dev/null
+++ b/src/backend/archive/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_archive.c'
+)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/archive/shell_archive.c
similarity index 78%
rename from src/backend/postmaster/shell_archive.c
rename to src/backend/archive/shell_archive.c
index 7771b951b7..48e8952c0c 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -18,30 +18,36 @@
#include <sys/wait.h>
#include "access/xlog.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b1db3ba75b..4fdd209b82 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv]
generated_backend_sources = []
subdir('access')
+subdir('archive')
subdir('backup')
subdir('bootstrap')
subdir('catalog')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 3a794e54d6..047448b34e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -22,7 +22,6 @@ OBJS = \
interrupt.o \
pgarch.o \
postmaster.o \
- shell_archive.o \
startup.o \
syslogger.o \
walwriter.o
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 9079922de7..cda921fd10 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
'interrupt.c',
'pgarch.c',
'postmaster.c',
- 'shell_archive.c',
'startup.c',
'syslogger.c',
'walwriter.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..46af349564 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -31,6 +31,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..91cb64602b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
#include "access/xlogrecovery.h"
+#include "archive/archive_module.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
new file mode 100644
index 0000000000..92ced4b222
--- /dev/null
+++ b/src/include/archive/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h
new file mode 100644
index 0000000000..1c843c13c5
--- /dev/null
+++ b/src/include/archive/shell_archive.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
Hi,
On 2023-02-13 14:56:47 -0800, Nathan Bossart wrote:
On Mon, Feb 13, 2023 at 04:37:10PM +0900, Michael Paquier wrote:
+ basic_archive_context = data->context; + if (CurrentMemoryContext == basic_archive_context) + MemoryContextSwitchTo(TopMemoryContext); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context);This is a bit confusing, because it means that we enter in the
shutdown callback with one context, but exit it under
TopMemoryContext. Are you sure that this will be OK when there could
be multiple callbacks piled up with before_shmem_exit()? shmem_exit()
has nothing specific to memory contexts.Well, we can't free the memory context while we are in it, so we have to
switch to another one. I agree that this is a bit confusing, though.
Why would we be in that memory context? I'd just add an assert documenting
we're not.
On second thought, I'm not sure it's important to make sure the state is
freed in the shutdown callback. It's only called just before the archiver
process exits, so we're not really at risk of leaking anything. I suppose
we might not always restart the archiver in this case, but I also don't
anticipate that behavior changing in the near future. I think this
callback is more useful for things like shutting down background workers.
I think it's crucial. Otherwise we're just ossifying the design that there's
just one archive module active at a time.
I went ahead and removed the shutdown callback from basic_archive and the
note about leaking from the documentation.
-1
Greetings,
Andres Freund
On Mon, Feb 13, 2023 at 03:37:33PM -0800, Andres Freund wrote:
On 2023-02-13 14:56:47 -0800, Nathan Bossart wrote:
On Mon, Feb 13, 2023 at 04:37:10PM +0900, Michael Paquier wrote:
+ basic_archive_context = data->context; + if (CurrentMemoryContext == basic_archive_context) + MemoryContextSwitchTo(TopMemoryContext); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context);This is a bit confusing, because it means that we enter in the
shutdown callback with one context, but exit it under
TopMemoryContext. Are you sure that this will be OK when there could
be multiple callbacks piled up with before_shmem_exit()? shmem_exit()
has nothing specific to memory contexts.Well, we can't free the memory context while we are in it, so we have to
switch to another one. I agree that this is a bit confusing, though.Why would we be in that memory context? I'd just add an assert documenting
we're not.On second thought, I'm not sure it's important to make sure the state is
freed in the shutdown callback. It's only called just before the archiver
process exits, so we're not really at risk of leaking anything. I suppose
we might not always restart the archiver in this case, but I also don't
anticipate that behavior changing in the near future. I think this
callback is more useful for things like shutting down background workers.I think it's crucial. Otherwise we're just ossifying the design that there's
just one archive module active at a time.I went ahead and removed the shutdown callback from basic_archive and the
note about leaking from the documentation.-1
Okay. I've added it back in v12 with the suggested adjustment for the
memory context stuff.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v12-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 22e583fa5b41d820e3233d87ea1306ff81349574 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v12 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 85 ++++++++++++++++---
doc/src/sgml/archive-modules.sgml | 35 ++++++--
src/backend/Makefile | 2 +-
src/backend/archive/Makefile | 18 ++++
src/backend/archive/meson.build | 5 ++
.../{postmaster => archive}/shell_archive.c | 30 ++++---
src/backend/meson.build | 1 +
src/backend/postmaster/Makefile | 1 -
src/backend/postmaster/meson.build | 1 -
src/backend/postmaster/pgarch.c | 27 +++---
src/backend/utils/misc/guc_tables.c | 1 +
src/include/archive/archive_module.h | 58 +++++++++++++
src/include/archive/shell_archive.h | 22 +++++
src/include/postmaster/pgarch.h | 39 ---------
14 files changed, 239 insertions(+), 86 deletions(-)
create mode 100644 src/backend/archive/Makefile
create mode 100644 src/backend/archive/meson.build
rename src/backend/{postmaster => archive}/shell_archive.c (78%)
create mode 100644 src/include/archive/archive_module.h
create mode 100644 src/include/archive/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..d7c227a10b 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -30,9 +30,9 @@
#include <sys/time.h>
#include <unistd.h>
+#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,14 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static void basic_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = basic_archive_shutdown
+};
/*
* _PG_init
@@ -67,10 +80,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +87,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +159,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +170,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
@@ -366,3 +394,34 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_archive_shutdown
+ *
+ * Frees our allocated state.
+ */
+static void
+basic_archive_shutdown(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context;
+
+ /*
+ * If we didn't get to storing the pointer to our allocated state, we don't
+ * have anything to clean up.
+ */
+ if (data == NULL)
+ return;
+
+ basic_archive_context = data->context;
+ Assert(CurrentMemoryContext != basic_archive_context);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
+
+ /*
+ * Finally, free the state.
+ */
+ pfree(data);
+ state->private_data = NULL;
+}
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..668eb34991 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -125,10 +143,11 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
process exits (e.g., after an error) or the value of
<xref linkend="guc-archive-library"/> changes. If no
<function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 86e6dcb792..e4bf0fe9c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -17,7 +17,7 @@ subdir = src/backend
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = access backup bootstrap catalog parser commands executor \
+SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile
new file mode 100644
index 0000000000..8d2860d0df
--- /dev/null
+++ b/src/backend/archive/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/archive
+#
+# IDENTIFICATION
+# src/backend/archive/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/archive
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_archive.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build
new file mode 100644
index 0000000000..e194282931
--- /dev/null
+++ b/src/backend/archive/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_archive.c'
+)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/archive/shell_archive.c
similarity index 78%
rename from src/backend/postmaster/shell_archive.c
rename to src/backend/archive/shell_archive.c
index 7771b951b7..48e8952c0c 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -18,30 +18,36 @@
#include <sys/wait.h>
#include "access/xlog.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b1db3ba75b..4fdd209b82 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv]
generated_backend_sources = []
subdir('access')
+subdir('archive')
subdir('backup')
subdir('bootstrap')
subdir('catalog')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 3a794e54d6..047448b34e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -22,7 +22,6 @@ OBJS = \
interrupt.o \
pgarch.o \
postmaster.o \
- shell_archive.o \
startup.o \
syslogger.o \
walwriter.o
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 9079922de7..cda921fd10 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
'interrupt.c',
'pgarch.c',
'postmaster.c',
- 'shell_archive.c',
'startup.c',
'syslogger.c',
'walwriter.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..46af349564 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -31,6 +31,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..91cb64602b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
#include "access/xlogrecovery.h"
+#include "archive/archive_module.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
new file mode 100644
index 0000000000..92ced4b222
--- /dev/null
+++ b/src/include/archive/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h
new file mode 100644
index 0000000000..1c843c13c5
--- /dev/null
+++ b/src/include/archive/shell_archive.h
@@ -0,0 +1,22 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
On Mon, Feb 13, 2023 at 04:55:58PM -0800, Nathan Bossart wrote:
Okay. I've added it back in v12 with the suggested adjustment for the
memory context stuff.
Sorry for then noise, cfbot alerted me to a missing #include, which I've
added in v13.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v13-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 41619a415a5a544a8a5b1e16ccbadac69a997027 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v13 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 85 ++++++++++++++++---
doc/src/sgml/archive-modules.sgml | 35 ++++++--
src/backend/Makefile | 2 +-
src/backend/archive/Makefile | 18 ++++
src/backend/archive/meson.build | 5 ++
.../{postmaster => archive}/shell_archive.c | 30 ++++---
src/backend/meson.build | 1 +
src/backend/postmaster/Makefile | 1 -
src/backend/postmaster/meson.build | 1 -
src/backend/postmaster/pgarch.c | 27 +++---
src/backend/utils/misc/guc_tables.c | 1 +
src/include/archive/archive_module.h | 58 +++++++++++++
src/include/archive/shell_archive.h | 24 ++++++
src/include/postmaster/pgarch.h | 39 ---------
14 files changed, 241 insertions(+), 86 deletions(-)
create mode 100644 src/backend/archive/Makefile
create mode 100644 src/backend/archive/meson.build
rename src/backend/{postmaster => archive}/shell_archive.c (78%)
create mode 100644 src/include/archive/archive_module.h
create mode 100644 src/include/archive/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..d7c227a10b 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -30,9 +30,9 @@
#include <sys/time.h>
#include <unistd.h>
+#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,14 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static void basic_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = basic_archive_shutdown
+};
/*
* _PG_init
@@ -67,10 +80,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +87,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +159,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +170,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
@@ -366,3 +394,34 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_archive_shutdown
+ *
+ * Frees our allocated state.
+ */
+static void
+basic_archive_shutdown(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context;
+
+ /*
+ * If we didn't get to storing the pointer to our allocated state, we don't
+ * have anything to clean up.
+ */
+ if (data == NULL)
+ return;
+
+ basic_archive_context = data->context;
+ Assert(CurrentMemoryContext != basic_archive_context);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
+
+ /*
+ * Finally, free the state.
+ */
+ pfree(data);
+ state->private_data = NULL;
+}
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..668eb34991 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -125,10 +143,11 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
process exits (e.g., after an error) or the value of
<xref linkend="guc-archive-library"/> changes. If no
<function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 86e6dcb792..e4bf0fe9c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -17,7 +17,7 @@ subdir = src/backend
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = access backup bootstrap catalog parser commands executor \
+SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile
new file mode 100644
index 0000000000..8d2860d0df
--- /dev/null
+++ b/src/backend/archive/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/archive
+#
+# IDENTIFICATION
+# src/backend/archive/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/archive
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_archive.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build
new file mode 100644
index 0000000000..e194282931
--- /dev/null
+++ b/src/backend/archive/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_archive.c'
+)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/archive/shell_archive.c
similarity index 78%
rename from src/backend/postmaster/shell_archive.c
rename to src/backend/archive/shell_archive.c
index 7771b951b7..48e8952c0c 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -18,30 +18,36 @@
#include <sys/wait.h>
#include "access/xlog.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b1db3ba75b..4fdd209b82 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv]
generated_backend_sources = []
subdir('access')
+subdir('archive')
subdir('backup')
subdir('bootstrap')
subdir('catalog')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 3a794e54d6..047448b34e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -22,7 +22,6 @@ OBJS = \
interrupt.o \
pgarch.o \
postmaster.o \
- shell_archive.o \
startup.o \
syslogger.o \
walwriter.o
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 9079922de7..cda921fd10 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
'interrupt.c',
'pgarch.c',
'postmaster.c',
- 'shell_archive.c',
'startup.c',
'syslogger.c',
'walwriter.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..46af349564 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -31,6 +31,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index b46e3b8c55..91cb64602b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
#include "access/xlogrecovery.h"
+#include "archive/archive_module.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
new file mode 100644
index 0000000000..92ced4b222
--- /dev/null
+++ b/src/include/archive/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h
new file mode 100644
index 0000000000..9de6f769f1
--- /dev/null
+++ b/src/include/archive/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "archive/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
On Mon, Feb 13, 2023 at 05:02:37PM -0800, Nathan Bossart wrote:
Sorry for then noise, cfbot alerted me to a missing #include, which I've
added in v13.
+ basic_archive_context = data->context;
+ Assert(CurrentMemoryContext != basic_archive_context);
So this is what it means to document that we are not in the memory
context we are freeing here. That seems good enough to me in this
context. Tracking if one of CurrentMemoryContext's parents is the
memory context that would be deleted would be another thing, but this
does not apply here.
I may tweak a bit the comments, but nothing more. And I don't think I
have more to add. Andres, do you have anything you would like to
mention?
--
Michael
On Wed, Feb 15, 2023 at 03:38:21PM +0900, Michael Paquier wrote:
On Mon, Feb 13, 2023 at 05:02:37PM -0800, Nathan Bossart wrote:
Sorry for then noise, cfbot alerted me to a missing #include, which I've
added in v13.
Sorry for more noise. I noticed that I missed updating the IDENTIFICATION
line for shell_archive.c. That's the only change in v14.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v14-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 2f32fdd5cfd89600025000c1ddfee30b6f9103b4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v14 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 85 ++++++++++++++++---
doc/src/sgml/archive-modules.sgml | 35 ++++++--
src/backend/Makefile | 2 +-
src/backend/archive/Makefile | 18 ++++
src/backend/archive/meson.build | 5 ++
.../{postmaster => archive}/shell_archive.c | 32 ++++---
src/backend/meson.build | 1 +
src/backend/postmaster/Makefile | 1 -
src/backend/postmaster/meson.build | 1 -
src/backend/postmaster/pgarch.c | 27 +++---
src/backend/utils/misc/guc_tables.c | 1 +
src/include/archive/archive_module.h | 58 +++++++++++++
src/include/archive/shell_archive.h | 24 ++++++
src/include/postmaster/pgarch.h | 39 ---------
14 files changed, 242 insertions(+), 87 deletions(-)
create mode 100644 src/backend/archive/Makefile
create mode 100644 src/backend/archive/meson.build
rename src/backend/{postmaster => archive}/shell_archive.c (77%)
create mode 100644 src/include/archive/archive_module.h
create mode 100644 src/include/archive/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..d7c227a10b 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -30,9 +30,9 @@
#include <sys/time.h>
#include <unistd.h>
+#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,14 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static void basic_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = basic_archive_shutdown
+};
/*
* _PG_init
@@ -67,10 +80,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +87,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +159,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +170,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
@@ -366,3 +394,34 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_archive_shutdown
+ *
+ * Frees our allocated state.
+ */
+static void
+basic_archive_shutdown(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
+ MemoryContext basic_archive_context;
+
+ /*
+ * If we didn't get to storing the pointer to our allocated state, we don't
+ * have anything to clean up.
+ */
+ if (data == NULL)
+ return;
+
+ basic_archive_context = data->context;
+ Assert(CurrentMemoryContext != basic_archive_context);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
+
+ /*
+ * Finally, free the state.
+ */
+ pfree(data);
+ state->private_data = NULL;
+}
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..668eb34991 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -125,10 +143,11 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
process exits (e.g., after an error) or the value of
<xref linkend="guc-archive-library"/> changes. If no
<function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 86e6dcb792..e4bf0fe9c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -17,7 +17,7 @@ subdir = src/backend
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = access backup bootstrap catalog parser commands executor \
+SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile
new file mode 100644
index 0000000000..8d2860d0df
--- /dev/null
+++ b/src/backend/archive/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/archive
+#
+# IDENTIFICATION
+# src/backend/archive/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/archive
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_archive.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build
new file mode 100644
index 0000000000..e194282931
--- /dev/null
+++ b/src/backend/archive/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_archive.c'
+)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/archive/shell_archive.c
similarity index 77%
rename from src/backend/postmaster/shell_archive.c
rename to src/backend/archive/shell_archive.c
index 7771b951b7..837da543f9 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -9,7 +9,7 @@
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * src/backend/postmaster/shell_archive.c
+ * src/backend/archive/shell_archive.c
*
*-------------------------------------------------------------------------
*/
@@ -18,30 +18,36 @@
#include <sys/wait.h>
#include "access/xlog.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b1db3ba75b..4fdd209b82 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv]
generated_backend_sources = []
subdir('access')
+subdir('archive')
subdir('backup')
subdir('bootstrap')
subdir('catalog')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 3a794e54d6..047448b34e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -22,7 +22,6 @@ OBJS = \
interrupt.o \
pgarch.o \
postmaster.o \
- shell_archive.o \
startup.o \
syslogger.o \
walwriter.o
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 9079922de7..cda921fd10 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
'interrupt.c',
'pgarch.c',
'postmaster.c',
- 'shell_archive.c',
'startup.c',
'syslogger.c',
'walwriter.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..46af349564 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -31,6 +31,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 43b9d92660..1c0583fe26 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
#include "access/xlogrecovery.h"
+#include "archive/archive_module.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
new file mode 100644
index 0000000000..92ced4b222
--- /dev/null
+++ b/src/include/archive/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h
new file mode 100644
index 0000000000..9de6f769f1
--- /dev/null
+++ b/src/include/archive/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "archive/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
Hi,
On 2023-02-15 10:44:07 -0800, Nathan Bossart wrote:
On Wed, Feb 15, 2023 at 03:38:21PM +0900, Michael Paquier wrote:
I may tweak a bit the comments, but nothing more. And I don't think I
have more to add. Andres, do you have anything you would like to
mention?
Just some minor comments below. None of them needs to be addressed.
@@ -144,10 +170,12 @@ basic_archive_configured(void) * Archives one file. */ static bool -basic_archive_file(const char *file, const char *path) +basic_archive_file(ArchiveModuleState *state, const char *file, const char *path) { sigjmp_buf local_sigjmp_buf;
Not related the things changed here, but this should never have been pushed
down into individual archive modules. There's absolutely no way that we're
going to keep this up2date and working correctly in random archive
modules. And it would break if archive modules are ever called outside of
pgarch.c.
+static void +basic_archive_shutdown(ArchiveModuleState *state) +{ + BasicArchiveData *data = (BasicArchiveData *) (state->private_data);
The parens around (state->private_data) are imo odd.
+ basic_archive_context = data->context; + Assert(CurrentMemoryContext != basic_archive_context); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context);
I guess I'd personally be paranoid and clean data->context after
this. Obviously doesn't matter right now, but at some later date it could be
that we'd error out after this point, and re-entered the shutdown callback.
+ +/* + * Archive module callbacks + * + * These callback functions should be defined by archive libraries and returned + * via _PG_archive_module_init(). ArchiveFileCB is the only required callback. + * For more information about the purpose of each callback, refer to the + * archive modules documentation. + */ +typedef void (*ArchiveStartupCB) (ArchiveModuleState *state); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); +typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); + +typedef struct ArchiveModuleCallbacks +{ + ArchiveStartupCB startup_cb; + ArchiveCheckConfiguredCB check_configured_cb; + ArchiveFileCB archive_file_cb; + ArchiveShutdownCB shutdown_cb; +} ArchiveModuleCallbacks;
If you wanted you could just define the callback types in the struct now, as
we don't need asserts for the types.
Greetings,
Andres Freund
Thanks for reviewing.
On Thu, Feb 16, 2023 at 11:29:56AM -0800, Andres Freund wrote:
On 2023-02-15 10:44:07 -0800, Nathan Bossart wrote:
@@ -144,10 +170,12 @@ basic_archive_configured(void) * Archives one file. */ static bool -basic_archive_file(const char *file, const char *path) +basic_archive_file(ArchiveModuleState *state, const char *file, const char *path) { sigjmp_buf local_sigjmp_buf;Not related the things changed here, but this should never have been pushed
down into individual archive modules. There's absolutely no way that we're
going to keep this up2date and working correctly in random archive
modules. And it would break if archive modules are ever called outside of
pgarch.c.
Yeah. IIRC I did briefly try to avoid this, but the difficulty was that
each module will have its own custom cleanup logic. There's no requirement
that a module creates an exception handler, but I imagine that any
sufficiently complex one will. In any case, I agree that it's worth trying
to pull this out of the individual modules.
+static void +basic_archive_shutdown(ArchiveModuleState *state) +{ + BasicArchiveData *data = (BasicArchiveData *) (state->private_data);The parens around (state->private_data) are imo odd.
Oops, removed.
+ basic_archive_context = data->context; + Assert(CurrentMemoryContext != basic_archive_context); + + if (MemoryContextIsValid(basic_archive_context)) + MemoryContextDelete(basic_archive_context);I guess I'd personally be paranoid and clean data->context after
this. Obviously doesn't matter right now, but at some later date it could be
that we'd error out after this point, and re-entered the shutdown callback.
Done.
+/* + * Archive module callbacks + * + * These callback functions should be defined by archive libraries and returned + * via _PG_archive_module_init(). ArchiveFileCB is the only required callback. + * For more information about the purpose of each callback, refer to the + * archive modules documentation. + */ +typedef void (*ArchiveStartupCB) (ArchiveModuleState *state); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); +typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); + +typedef struct ArchiveModuleCallbacks +{ + ArchiveStartupCB startup_cb; + ArchiveCheckConfiguredCB check_configured_cb; + ArchiveFileCB archive_file_cb; + ArchiveShutdownCB shutdown_cb; +} ArchiveModuleCallbacks;If you wanted you could just define the callback types in the struct now, as
we don't need asserts for the types.
This crossed my mind. I thought it was nice to have a declaration for each
callback that we can copy into the docs, but I'm sure we could do without
it, too.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v15-0001-restructure-archive-modules-API.patchtext/x-diff; charset=us-asciiDownload
From 5969db5576f9d475169f1b0e25fcb4d9d331c9f4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 9 Feb 2023 13:49:46 -0800
Subject: [PATCH v15 1/1] restructure archive modules API
---
contrib/basic_archive/basic_archive.c | 86 ++++++++++++++++---
doc/src/sgml/archive-modules.sgml | 35 ++++++--
src/backend/Makefile | 2 +-
src/backend/archive/Makefile | 18 ++++
src/backend/archive/meson.build | 5 ++
.../{postmaster => archive}/shell_archive.c | 32 ++++---
src/backend/meson.build | 1 +
src/backend/postmaster/Makefile | 1 -
src/backend/postmaster/meson.build | 1 -
src/backend/postmaster/pgarch.c | 27 +++---
src/backend/utils/misc/guc_tables.c | 1 +
src/include/archive/archive_module.h | 58 +++++++++++++
src/include/archive/shell_archive.h | 24 ++++++
src/include/postmaster/pgarch.h | 39 ---------
14 files changed, 243 insertions(+), 87 deletions(-)
create mode 100644 src/backend/archive/Makefile
create mode 100644 src/backend/archive/meson.build
rename src/backend/{postmaster => archive}/shell_archive.c (77%)
create mode 100644 src/include/archive/archive_module.h
create mode 100644 src/include/archive/shell_archive.h
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 36b7a4814a..cd852888ce 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -30,9 +30,9 @@
#include <sys/time.h>
#include <unistd.h>
+#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
-#include "postmaster/pgarch.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -40,14 +40,27 @@
PG_MODULE_MAGIC;
+typedef struct BasicArchiveData
+{
+ MemoryContext context;
+} BasicArchiveData;
+
static char *archive_directory = NULL;
-static MemoryContext basic_archive_context;
-static bool basic_archive_configured(void);
-static bool basic_archive_file(const char *file, const char *path);
+static void basic_archive_startup(ArchiveModuleState *state);
+static bool basic_archive_configured(ArchiveModuleState *state);
+static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static void basic_archive_shutdown(ArchiveModuleState *state);
+
+static const ArchiveModuleCallbacks basic_archive_callbacks = {
+ .startup_cb = basic_archive_startup,
+ .check_configured_cb = basic_archive_configured,
+ .archive_file_cb = basic_archive_file,
+ .shutdown_cb = basic_archive_shutdown
+};
/*
* _PG_init
@@ -67,10 +80,6 @@ _PG_init(void)
check_archive_directory, NULL, NULL);
MarkGUCPrefixReserved("basic_archive");
-
- basic_archive_context = AllocSetContextCreate(TopMemoryContext,
- "basic_archive",
- ALLOCSET_DEFAULT_SIZES);
}
/*
@@ -78,11 +87,28 @@ _PG_init(void)
*
* Returns the module's archiving callbacks.
*/
+const ArchiveModuleCallbacks *
+_PG_archive_module_init(void)
+{
+ return &basic_archive_callbacks;
+}
+
+/*
+ * basic_archive_startup
+ *
+ * Creates the module's memory context.
+ */
void
-_PG_archive_module_init(ArchiveModuleCallbacks *cb)
+basic_archive_startup(ArchiveModuleState *state)
{
- cb->check_configured_cb = basic_archive_configured;
- cb->archive_file_cb = basic_archive_file;
+ BasicArchiveData *data;
+
+ data = (BasicArchiveData *) MemoryContextAllocZero(TopMemoryContext,
+ sizeof(BasicArchiveData));
+ data->context = AllocSetContextCreate(TopMemoryContext,
+ "basic_archive",
+ ALLOCSET_DEFAULT_SIZES);
+ state->private_data = (void *) data;
}
/*
@@ -133,7 +159,7 @@ check_archive_directory(char **newval, void **extra, GucSource source)
* Checks that archive_directory is not blank.
*/
static bool
-basic_archive_configured(void)
+basic_archive_configured(ArchiveModuleState *state)
{
return archive_directory != NULL && archive_directory[0] != '\0';
}
@@ -144,10 +170,12 @@ basic_archive_configured(void)
* Archives one file.
*/
static bool
-basic_archive_file(const char *file, const char *path)
+basic_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext oldcontext;
+ BasicArchiveData *data = (BasicArchiveData *) state->private_data;
+ MemoryContext basic_archive_context = data->context;
/*
* We run basic_archive_file_internal() in our own memory context so that
@@ -366,3 +394,35 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_archive_shutdown
+ *
+ * Frees our allocated state.
+ */
+static void
+basic_archive_shutdown(ArchiveModuleState *state)
+{
+ BasicArchiveData *data = (BasicArchiveData *) state->private_data;
+ MemoryContext basic_archive_context;
+
+ /*
+ * If we didn't get to storing the pointer to our allocated state, we don't
+ * have anything to clean up.
+ */
+ if (data == NULL)
+ return;
+
+ basic_archive_context = data->context;
+ Assert(CurrentMemoryContext != basic_archive_context);
+
+ if (MemoryContextIsValid(basic_archive_context))
+ MemoryContextDelete(basic_archive_context);
+ data->context = NULL;
+
+ /*
+ * Finally, free the state.
+ */
+ pfree(data);
+ state->private_data = NULL;
+}
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-modules.sgml
index ef02051f7f..668eb34991 100644
--- a/doc/src/sgml/archive-modules.sgml
+++ b/doc/src/sgml/archive-modules.sgml
@@ -47,18 +47,22 @@
normal library search path is used to locate the library. To provide the
required archive module callbacks and to indicate that the library is
actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. This function is passed a
- struct that needs to be filled with the callback function pointers for
- individual actions.
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
{
+ ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
-typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
Only the <function>archive_file_cb</function> callback is required. The
@@ -73,6 +77,20 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
The server will call them as required to process each individual WAL file.
</para>
+ <sect2 id="archive-module-startup">
+ <title>Startup Callback</title>
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+</programlisting>
+ </para>
+ </sect2>
+
<sect2 id="archive-module-check">
<title>Check Callback</title>
<para>
@@ -83,7 +101,7 @@ typedef void (*ArchiveModuleInit) (struct ArchiveModuleCallbacks *cb);
assumes the module is configured.
<programlisting>
-typedef bool (*ArchiveCheckConfiguredCB) (void);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
If <literal>true</literal> is returned, the server will proceed with
@@ -105,7 +123,7 @@ WARNING: archive_mode enabled, yet archiving is not configured
single WAL file.
<programlisting>
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
If <literal>true</literal> is returned, the server proceeds as if the file
@@ -125,10 +143,11 @@ typedef bool (*ArchiveFileCB) (const char *file, const char *path);
process exits (e.g., after an error) or the value of
<xref linkend="guc-archive-library"/> changes. If no
<function>shutdown_cb</function> is defined, no special action is taken in
- these situations.
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
-typedef void (*ArchiveShutdownCB) (void);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
</para>
</sect2>
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 86e6dcb792..e4bf0fe9c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -17,7 +17,7 @@ subdir = src/backend
top_builddir = ../..
include $(top_builddir)/src/Makefile.global
-SUBDIRS = access backup bootstrap catalog parser commands executor \
+SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
regex replication rewrite \
diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile
new file mode 100644
index 0000000000..8d2860d0df
--- /dev/null
+++ b/src/backend/archive/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/archive
+#
+# IDENTIFICATION
+# src/backend/archive/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/archive
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_archive.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build
new file mode 100644
index 0000000000..e194282931
--- /dev/null
+++ b/src/backend/archive/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_archive.c'
+)
diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/archive/shell_archive.c
similarity index 77%
rename from src/backend/postmaster/shell_archive.c
rename to src/backend/archive/shell_archive.c
index 7771b951b7..837da543f9 100644
--- a/src/backend/postmaster/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -9,7 +9,7 @@
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * src/backend/postmaster/shell_archive.c
+ * src/backend/archive/shell_archive.c
*
*-------------------------------------------------------------------------
*/
@@ -18,30 +18,36 @@
#include <sys/wait.h>
#include "access/xlog.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "common/percentrepl.h"
#include "pgstat.h"
-#include "postmaster/pgarch.h"
-static bool shell_archive_configured(void);
-static bool shell_archive_file(const char *file, const char *path);
-static void shell_archive_shutdown(void);
+static bool shell_archive_configured(ArchiveModuleState *state);
+static bool shell_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static void shell_archive_shutdown(ArchiveModuleState *state);
-void
-shell_archive_init(ArchiveModuleCallbacks *cb)
+static const ArchiveModuleCallbacks shell_archive_callbacks = {
+ .startup_cb = NULL,
+ .check_configured_cb = shell_archive_configured,
+ .archive_file_cb = shell_archive_file,
+ .shutdown_cb = shell_archive_shutdown
+};
+
+const ArchiveModuleCallbacks *
+shell_archive_init(void)
{
- cb->check_configured_cb = shell_archive_configured;
- cb->archive_file_cb = shell_archive_file;
- cb->shutdown_cb = shell_archive_shutdown;
+ return &shell_archive_callbacks;
}
static bool
-shell_archive_configured(void)
+shell_archive_configured(ArchiveModuleState *state)
{
return XLogArchiveCommand[0] != '\0';
}
static bool
-shell_archive_file(const char *file, const char *path)
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
{
char *xlogarchcmd;
char *nativePath = NULL;
@@ -123,7 +129,7 @@ shell_archive_file(const char *file, const char *path)
}
static void
-shell_archive_shutdown(void)
+shell_archive_shutdown(ArchiveModuleState *state)
{
elog(DEBUG1, "archiver process shutting down");
}
diff --git a/src/backend/meson.build b/src/backend/meson.build
index b1db3ba75b..4fdd209b82 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv]
generated_backend_sources = []
subdir('access')
+subdir('archive')
subdir('backup')
subdir('bootstrap')
subdir('catalog')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 3a794e54d6..047448b34e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -22,7 +22,6 @@ OBJS = \
interrupt.o \
pgarch.o \
postmaster.o \
- shell_archive.o \
startup.o \
syslogger.o \
walwriter.o
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 9079922de7..cda921fd10 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -10,7 +10,6 @@ backend_sources += files(
'interrupt.c',
'pgarch.c',
'postmaster.c',
- 'shell_archive.c',
'startup.c',
'syslogger.c',
'walwriter.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index e551af2905..46af349564 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -31,6 +31,8 @@
#include "access/xlog.h"
#include "access/xlog_internal.h"
+#include "archive/archive_module.h"
+#include "archive/shell_archive.h"
#include "lib/binaryheap.h"
#include "libpq/pqsignal.h"
#include "pgstat.h"
@@ -97,7 +99,8 @@ char *XLogArchiveLibrary = "";
*/
static time_t last_sigterm_time = 0;
static PgArchData *PgArch = NULL;
-static ArchiveModuleCallbacks ArchiveContext;
+static const ArchiveModuleCallbacks *ArchiveCallbacks;
+static ArchiveModuleState *archive_module_state;
/*
@@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void)
HandlePgArchInterrupts();
/* can't do anything if not configured ... */
- if (ArchiveContext.check_configured_cb != NULL &&
- !ArchiveContext.check_configured_cb())
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
{
ereport(WARNING,
(errmsg("archive_mode enabled, yet archiving is not configured")));
@@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog)
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- ret = ArchiveContext.archive_file_cb(xlog, pathname);
+ ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
if (ret)
snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
else
@@ -814,7 +817,7 @@ HandlePgArchInterrupts(void)
/*
* LoadArchiveLibrary
*
- * Loads the archiving callbacks into our local ArchiveContext.
+ * Loads the archiving callbacks into our local ArchiveCallbacks.
*/
static void
LoadArchiveLibrary(void)
@@ -827,8 +830,6 @@ LoadArchiveLibrary(void)
errmsg("both archive_command and archive_library set"),
errdetail("Only one of archive_command, archive_library may be set.")));
- memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks));
-
/*
* If shell archiving is enabled, use our special initialization function.
* Otherwise, load the library and call its _PG_archive_module_init().
@@ -844,12 +845,16 @@ LoadArchiveLibrary(void)
ereport(ERROR,
(errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init")));
- (*archive_init) (&ArchiveContext);
+ ArchiveCallbacks = (*archive_init) ();
- if (ArchiveContext.archive_file_cb == NULL)
+ if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
+ archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState));
+ if (ArchiveCallbacks->startup_cb != NULL)
+ ArchiveCallbacks->startup_cb(archive_module_state);
+
before_shmem_exit(pgarch_call_module_shutdown_cb, 0);
}
@@ -859,6 +864,6 @@ LoadArchiveLibrary(void)
static void
pgarch_call_module_shutdown_cb(int code, Datum arg)
{
- if (ArchiveContext.shutdown_cb != NULL)
- ArchiveContext.shutdown_cb();
+ if (ArchiveCallbacks->shutdown_cb != NULL)
+ ArchiveCallbacks->shutdown_cb(archive_module_state);
}
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 43b9d92660..1c0583fe26 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -33,6 +33,7 @@
#include "access/xlog_internal.h"
#include "access/xlogprefetcher.h"
#include "access/xlogrecovery.h"
+#include "archive/archive_module.h"
#include "catalog/namespace.h"
#include "catalog/storage.h"
#include "commands/async.h"
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
new file mode 100644
index 0000000000..92ced4b222
--- /dev/null
+++ b/src/include/archive/archive_module.h
@@ -0,0 +1,58 @@
+/*-------------------------------------------------------------------------
+ *
+ * archive_module.h
+ * Exports for archive modules.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/archive_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _ARCHIVE_MODULE_H
+#define _ARCHIVE_MODULE_H
+
+/*
+ * The value of the archive_library GUC.
+ */
+extern PGDLLIMPORT char *XLogArchiveLibrary;
+
+typedef struct ArchiveModuleState
+{
+ /*
+ * Private data pointer for use by an archive module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+} ArchiveModuleState;
+
+/*
+ * Archive module callbacks
+ *
+ * These callback functions should be defined by archive libraries and returned
+ * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
+ * For more information about the purpose of each callback, refer to the
+ * archive modules documentation.
+ */
+typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
+typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+typedef struct ArchiveModuleCallbacks
+{
+ ArchiveStartupCB startup_cb;
+ ArchiveCheckConfiguredCB check_configured_cb;
+ ArchiveFileCB archive_file_cb;
+ ArchiveShutdownCB shutdown_cb;
+} ArchiveModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_archive_module_init that is looked
+ * up when loading an archive library.
+ */
+typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
+
+extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void);
+
+#endif /* _ARCHIVE_MODULE_H */
diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h
new file mode 100644
index 0000000000..9de6f769f1
--- /dev/null
+++ b/src/include/archive/shell_archive.h
@@ -0,0 +1,24 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_archive.h
+ * Exports for archiving via shell.
+ *
+ * Copyright (c) 2022-2023, PostgreSQL Global Development Group
+ *
+ * src/include/archive/shell_archive.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_ARCHIVE_H
+#define _SHELL_ARCHIVE_H
+
+#include "archive/archive_module.h"
+
+/*
+ * Since the logic for archiving via a shell command is in the core server
+ * and does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const ArchiveModuleCallbacks *shell_archive_init(void);
+
+#endif /* _SHELL_ARCHIVE_H */
diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h
index bcd51dfad6..3bd4fac71e 100644
--- a/src/include/postmaster/pgarch.h
+++ b/src/include/postmaster/pgarch.h
@@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn();
extern void PgArchWakeup(void);
extern void PgArchForceDirScan(void);
-/*
- * The value of the archive_library GUC.
- */
-extern PGDLLIMPORT char *XLogArchiveLibrary;
-
-/*
- * Archive module callbacks
- *
- * These callback functions should be defined by archive libraries and returned
- * via _PG_archive_module_init(). ArchiveFileCB is the only required callback.
- * For more information about the purpose of each callback, refer to the
- * archive modules documentation.
- */
-typedef bool (*ArchiveCheckConfiguredCB) (void);
-typedef bool (*ArchiveFileCB) (const char *file, const char *path);
-typedef void (*ArchiveShutdownCB) (void);
-
-typedef struct ArchiveModuleCallbacks
-{
- ArchiveCheckConfiguredCB check_configured_cb;
- ArchiveFileCB archive_file_cb;
- ArchiveShutdownCB shutdown_cb;
-} ArchiveModuleCallbacks;
-
-/*
- * Type of the shared library symbol _PG_archive_module_init that is looked
- * up when loading an archive library.
- */
-typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb);
-
-extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb);
-
-/*
- * Since the logic for archiving via a shell command is in the core server
- * and does not need to be loaded via a shared library, it has a special
- * initialization function.
- */
-extern void shell_archive_init(ArchiveModuleCallbacks *cb);
-
#endif /* _PGARCH_H */
--
2.25.1
Hi,
On 2023-02-16 12:15:12 -0800, Nathan Bossart wrote:
Thanks for reviewing.
On Thu, Feb 16, 2023 at 11:29:56AM -0800, Andres Freund wrote:
On 2023-02-15 10:44:07 -0800, Nathan Bossart wrote:
@@ -144,10 +170,12 @@ basic_archive_configured(void) * Archives one file. */ static bool -basic_archive_file(const char *file, const char *path) +basic_archive_file(ArchiveModuleState *state, const char *file, const char *path) { sigjmp_buf local_sigjmp_buf;Not related the things changed here, but this should never have been pushed
down into individual archive modules. There's absolutely no way that we're
going to keep this up2date and working correctly in random archive
modules. And it would break if archive modules are ever called outside of
pgarch.c.Yeah. IIRC I did briefly try to avoid this, but the difficulty was that
each module will have its own custom cleanup logic.
It can use PG_TRY/CATCH for that, if the top-level sigsetjmp is in pgarch.c.
Or you could add a cleanup callback to the API, to be called after the
top-level cleanup in pgarch.c.
I'm quite baffled by:
/* Close any files left open by copy_file() or compare_files() */
AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId);
in basic_archive_file(). It seems *really* off to call AtEOSubXact_Files()
completely outside the context of a transaction environment. And it only does
the thing you want because you pass parameters that aren't actually valid in
the normal use in AtEOSubXact_Files(). I really don't understand how that's
supposed to be ok.
Greetings,
Andres Freund
On Thu, Feb 16, 2023 at 01:17:54PM -0800, Andres Freund wrote:
On 2023-02-16 12:15:12 -0800, Nathan Bossart wrote:
On Thu, Feb 16, 2023 at 11:29:56AM -0800, Andres Freund wrote:
On 2023-02-15 10:44:07 -0800, Nathan Bossart wrote:
@@ -144,10 +170,12 @@ basic_archive_configured(void) * Archives one file. */ static bool -basic_archive_file(const char *file, const char *path) +basic_archive_file(ArchiveModuleState *state, const char *file, const char *path) { sigjmp_buf local_sigjmp_buf;Not related the things changed here, but this should never have been pushed
down into individual archive modules. There's absolutely no way that we're
going to keep this up2date and working correctly in random archive
modules. And it would break if archive modules are ever called outside of
pgarch.c.Yeah. IIRC I did briefly try to avoid this, but the difficulty was that
each module will have its own custom cleanup logic.It can use PG_TRY/CATCH for that, if the top-level sigsetjmp is in pgarch.c.
Or you could add a cleanup callback to the API, to be called after the
top-level cleanup in pgarch.c.
Yeah, that seems workable.
I'm quite baffled by:
/* Close any files left open by copy_file() or compare_files() */
AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId);in basic_archive_file(). It seems *really* off to call AtEOSubXact_Files()
completely outside the context of a transaction environment. And it only does
the thing you want because you pass parameters that aren't actually valid in
the normal use in AtEOSubXact_Files(). I really don't understand how that's
supposed to be ok.
Hm. Should copy_file() and compare_files() have PG_FINALLY blocks that
attempt to close the files instead? What would you recommend?
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
On Thu, Feb 16, 2023 at 01:17:54PM -0800, Andres Freund wrote:
On 2023-02-16 12:15:12 -0800, Nathan Bossart wrote:
On Thu, Feb 16, 2023 at 11:29:56AM -0800, Andres Freund wrote:
Not related the things changed here, but this should never have been pushed
down into individual archive modules. There's absolutely no way that we're
going to keep this up2date and working correctly in random archive
modules. And it would break if archive modules are ever called outside of
pgarch.c.
Hmm, yes. That's a bad idea to copy the error handling stack. The
maintenance of this code could quickly go wrong. All that had better
be put into their own threads, IMO, to bring more visibility on these
subjects.
I'm quite baffled by:
/* Close any files left open by copy_file() or compare_files() */
AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId);in basic_archive_file(). It seems *really* off to call AtEOSubXact_Files()
completely outside the context of a transaction environment. And it only does
the thing you want because you pass parameters that aren't actually valid in
the normal use in AtEOSubXact_Files(). I really don't understand how that's
supposed to be ok.
As does this part, probably with a backpatch..
Saying that, I have spent more time on the revamped version of the
archive modules and it was already doing a lot, so I have applied
it as it covered all the points discussed..
--
Michael
Hi,
On 2023-02-16 13:58:10 -0800, Nathan Bossart wrote:
On Thu, Feb 16, 2023 at 01:17:54PM -0800, Andres Freund wrote:
I'm quite baffled by:
/* Close any files left open by copy_file() or compare_files() */
AtEOSubXact_Files(false, InvalidSubTransactionId, InvalidSubTransactionId);in basic_archive_file(). It seems *really* off to call AtEOSubXact_Files()
completely outside the context of a transaction environment. And it only does
the thing you want because you pass parameters that aren't actually valid in
the normal use in AtEOSubXact_Files(). I really don't understand how that's
supposed to be ok.Hm. Should copy_file() and compare_files() have PG_FINALLY blocks that
attempt to close the files instead? What would you recommend?
I don't fully now, it's not entirely clear to me what the goals here were. I
think you'd likely need to do a bit of infrastructure work to do this
sanely. So far we just didn't have the need to handle files being released in
a way like you want to do there.
I suspect a good direction would be to use resource owners. Add a separate set
of functions that release files on resource owner release. Most of the
infrastructure is there already, for temporary files
(c.f. OpenTemporaryFile()).
Then that resource owner could be reset in case of error.
I'm not even sure that erroring out is a reasonable way to implement
copy_file(), compare_files(), particularly because you want to return via a
return code from basic_archive_files().
Greetings,
Andres Freund
On Fri, Feb 17, 2023 at 05:01:47PM +0900, Michael Paquier wrote:
All that had better
be put into their own threads, IMO, to bring more visibility on these
subjects.
I created a new thread for these [0]/messages/by-id/20230217215624.GA3131134@nathanxps13.
Saying that, I have spent more time on the revamped version of the
archive modules and it was already doing a lot, so I have applied
it as it covered all the points discussed..
Thanks!
[0]: /messages/by-id/20230217215624.GA3131134@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Here is a new revision of the restore modules patch set. In this patch
set, the interface looks similar to the recent archive modules redesign,
and there are separate callbacks for retrieving different types of files.
I've attempted to address all the feedback I've received, but there was a
lot scattered across different threads, so it's possible I've missed
something. Note that 0001 is the stopgap fix for restore_command that's
being tracked elsewhere [0]/messages/by-id/20230214174755.GA1348509@nathanxps13. I was careful to avoid repeating the recent
mistake with the SIGTERM handling.
This patch set is still a little rough around the edges, but I wanted to
post it in case folks had general thoughts about the structure, interface,
etc. This implementation restores files synchronously one-by-one just like
archive modules, but in the future, I would like to add
asynchronous/parallel/batching support. My intent is for this work to move
us closer to that.
[0]: /messages/by-id/20230214174755.GA1348509@nathanxps13
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v12-0001-stopgap-fix-for-restore_command.patchtext/x-diff; charset=us-asciiDownload
From 64651ae3c56160fea31d15a78f07c8c12950ec99 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 14 Feb 2023 09:44:53 -0800
Subject: [PATCH v12 1/6] stopgap fix for restore_command
---
src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
src/backend/postmaster/startup.c | 20 +++++++++++++++++++-
src/backend/storage/ipc/ipc.c | 3 +++
src/backend/storage/lmgr/proc.c | 2 ++
4 files changed, 35 insertions(+), 5 deletions(-)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
(errmsg_internal("executing restore command \"%s\"",
xlogRestoreCmd)));
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
/*
- * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
PostRestoreCommand();
+
+ pgstat_report_wait_end();
pfree(xlogRestoreCmd);
if (rc == 0)
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index efc2580536..de2b56c2fa 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,6 +19,8 @@
*/
#include "postgres.h"
+#include <unistd.h>
+
#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
@@ -121,7 +123,23 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
int save_errno = errno;
if (in_restore_command)
- proc_exit(1);
+ {
+ /*
+ * If we are in a child process (e.g., forked by system() in
+ * RestoreArchivedFile()), we don't want to call any exit callbacks.
+ * The parent will take care of that.
+ */
+ if (MyProcPid == (int) getpid())
+ proc_exit(1);
+ else
+ {
+ const char msg[] = "StartupProcShutdownHandler() called in child process";
+ int rc pg_attribute_unused();
+
+ rc = write(STDERR_FILENO, msg, sizeof(msg));
+ _exit(1);
+ }
+ }
else
shutdown_requested = true;
WakeupRecovery();
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 1904d21795..6796cabc3e 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -103,6 +103,9 @@ static int on_proc_exit_index,
void
proc_exit(int code)
{
+ /* proc_exit() is not safe in forked processes from system(), etc. */
+ Assert(MyProcPid == getpid());
+
/* Clean up everything that must be cleaned up */
proc_exit_prepare(code);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 22b4278610..ae845e8249 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -805,6 +805,7 @@ ProcKill(int code, Datum arg)
dlist_head *procgloballist;
Assert(MyProc != NULL);
+ Assert(MyProcPid == getpid()); /* not safe if forked by system(), etc. */
/* Make sure we're out of the sync rep lists */
SyncRepCleanupAtProcExit();
@@ -925,6 +926,7 @@ AuxiliaryProcKill(int code, Datum arg)
PGPROC *proc;
Assert(proctype >= 0 && proctype < NUM_AUXILIARY_PROCS);
+ Assert(MyProcPid == getpid()); /* not safe if forked by system(), etc. */
auxproc = &AuxiliaryProcs[proctype];
--
2.25.1
v12-0002-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 2ef84ed51ba934c7979e2a741981e5200ca6f093 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v12 2/6] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 46af349564..d94730c50c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..e780438948 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2610,6 +2610,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..7346a3f1ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v12-0003-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From e5b48074800b60cb567e17c7cd14979ec6512de7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v12 3/6] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 246 ++++++++++++++++++++++
src/include/access/xlogarchive.h | 9 +-
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
14 files changed, 406 insertions(+), 151 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index e4bf0fe9c0..23812e9a6a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -20,7 +20,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 94e152694e..b3e1c18c65 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f9f0f6db8d..c4268af688 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -692,6 +693,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4887,12 +4889,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7307,12 +7315,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -8889,6 +8903,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 41684418b6..4b45ea8753 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration of
- * the system() call because there isn't a good way to break out while it
- * is executing. Since we might call proc_exit() in a signal handler, it
- * is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index dbe9394762..f0e1007f92 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4106,7 +4106,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 4fdd209b82..d5d3ae7822 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -26,6 +26,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index de2b56c2fa..4648299ced 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -201,8 +201,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -226,6 +225,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -301,8 +310,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..2b27cfced0
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..fcd5475c88
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,246 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..b9bd0a93cd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 6a2e4c4526..09d3f89c69 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..570588e493
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 22ea42c16b..931c9acf45 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -132,6 +132,7 @@ ArchiveModuleState
ArchiveOpts
ArchiveShutdownCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v12-0004-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From b9b14e1394e738f2a09bb8532d6deb3bc7578df2 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v12 4/6] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 0d6be9a2fa..66a4de4762 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f1ff6bb451 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v12-0005-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 2a966fa63f61e50c878ce198bf1fb59bd5878f1a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v12 5/6] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 207 ++++++++++--------
1 file changed, 111 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7cf44e82e2..f30ee591e7 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,53 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know how to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +73,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has a state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has a state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v12-0006-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 20fd226d8842528dba5169a908a8ee163f4a7816 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v12 6/6] introduce restore_library
---
contrib/basic_archive/Makefile | 1 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 28 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 53 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 142 +++++++
src/include/restore/shell_restore.h | 16 +-
19 files changed, 1103 insertions(+), 88 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..d12d45e0d2 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -5,6 +5,7 @@ PGFILEDESC = "basic_archive - basic archive module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
+TAP_TESTS = 1
# Disabled because these tests require "shared_preload_libraries=basic_archive",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index cd852888ce..b04d83da4c 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..4e9f5002de 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..85b14c5113
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index f30ee591e7..56373a211b 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -167,4 +169,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..5555f23a85 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1218,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1255,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index ecd9aa73ef..8fec106a24 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3807,7 +3807,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3835,7 +3836,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3870,7 +3872,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3915,7 +3952,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3944,7 +3984,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index f180607528..957d427616 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c4268af688..b6fa39c7cf 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -4888,18 +4888,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7314,18 +7315,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b45ea8753..50bcda1120 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index f0e1007f92..c6645c2413 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1078,18 +1079,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index aaad5c5228..73ff592f9f 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -233,6 +234,24 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback, if
+ * one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking checkpoints
+ * or shutting down the server when the parameters are misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -548,6 +567,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +586,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides restarting
+ * the process, and there should be little harm in leaving it around,
+ * so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 4648299ced..91229d96be 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -29,6 +29,7 @@
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -151,13 +152,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -179,6 +184,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded shutdown
+ * callback and load the new callbacks. There's presently no good way to
+ * unload a library besides restarting the process, and there should be
+ * little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -288,6 +317,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index fcd5475c88..b67cd4ac48 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2023, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -153,8 +206,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -162,8 +215,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -173,8 +227,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -182,8 +236,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..a42819399b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -63,6 +63,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3788,6 +3789,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..53757fea5f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -270,6 +270,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..c0aef33ba5
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,142 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence merely
+ * copies the file. If set to true, the server will verify that the
+ * timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 570588e493..1eeae51d81 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
--
2.25.1
Here is a rebased version of the restore modules patch set. I swapped the
patch for the stopgap fix for restore_command with the latest version [0]/messages/by-id/20230301224751.GA1823946@nathanxps13,
and I marked the restore/ headers as installable (as was recently done for
archive/ [1]https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=6ad5793). There are no other changes.
[0]: /messages/by-id/20230301224751.GA1823946@nathanxps13
[1]: https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=6ad5793
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v13-0001-Move-extra-code-out-of-the-Pre-PostRestoreComman.patchtext/x-diff; charset=us-asciiDownload
From 1173c8b4e476575c3e4b410f3aa6220360c38503 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 23 Feb 2023 14:27:48 -0800
Subject: [PATCH v13 1/7] Move extra code out of the Pre/PostRestoreCommand()
block.
If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section. This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block. This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
(errmsg_internal("executing restore command \"%s\"",
xlogRestoreCmd)));
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
/*
- * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
PostRestoreCommand();
+
+ pgstat_report_wait_end();
pfree(xlogRestoreCmd);
if (rc == 0)
--
2.25.1
v13-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patchtext/x-diff; charset=us-asciiDownload
From bdd7268075f150bd292050f74701568af8eb66ec Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 14 Feb 2023 09:44:53 -0800
Subject: [PATCH v13 2/7] Don't proc_exit() in startup's SIGTERM handler if
forked by system().
Instead, emit a message to STDERR and _exit() in this case. This
change also adds assertions to proc_exit(), ProcKill(), and
AuxiliaryProcKill() to verify that these functions are not called
by a process forked by system(), etc.
---
src/backend/postmaster/startup.c | 17 ++++++++++++++++-
src/backend/storage/ipc/ipc.c | 3 +++
src/backend/storage/lmgr/proc.c | 2 ++
src/backend/utils/error/elog.c | 28 ++++++++++++++++++++++++++++
src/include/utils/elog.h | 6 +-----
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index efc2580536..0e7de26bc2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,6 +19,8 @@
*/
#include "postgres.h"
+#include <unistd.h>
+
#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
@@ -121,7 +123,20 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
int save_errno = errno;
if (in_restore_command)
- proc_exit(1);
+ {
+ /*
+ * If we are in a child process (e.g., forked by system() in
+ * RestoreArchivedFile()), we don't want to call any exit callbacks.
+ * The parent will take care of that.
+ */
+ if (MyProcPid == (int) getpid())
+ proc_exit(1);
+ else
+ {
+ write_stderr_signal_safe("StartupProcShutdownHandler() called in child process\n");
+ _exit(1);
+ }
+ }
else
shutdown_requested = true;
WakeupRecovery();
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 1904d21795..d5097dc008 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -103,6 +103,9 @@ static int on_proc_exit_index,
void
proc_exit(int code)
{
+ /* proc_exit() is not safe in forked processes from system(), etc. */
+ Assert(MyProcPid == (int) getpid());
+
/* Clean up everything that must be cleaned up */
proc_exit_prepare(code);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 22b4278610..b9e2c3aafe 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -805,6 +805,7 @@ ProcKill(int code, Datum arg)
dlist_head *procgloballist;
Assert(MyProc != NULL);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
/* Make sure we're out of the sync rep lists */
SyncRepCleanupAtProcExit();
@@ -925,6 +926,7 @@ AuxiliaryProcKill(int code, Datum arg)
PGPROC *proc;
Assert(proctype >= 0 && proctype < NUM_AUXILIARY_PROCS);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
auxproc = &AuxiliaryProcs[proctype];
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 5898100acb..f9925c8d8e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -3730,6 +3730,34 @@ write_stderr(const char *fmt,...)
}
+/*
+ * Write a message to STDERR using only async-signal-safe functions. This can
+ * be used to safely emit a message from a signal handler.
+ *
+ * TODO: It is likely possible to safely do a limited amount of string
+ * interpolation (e.g., %s and %d), but that is not presently supported.
+ */
+void
+write_stderr_signal_safe(const char *fmt)
+{
+ int nwritten = 0;
+ int ntotal = strlen(fmt);
+
+ while (nwritten < ntotal)
+ {
+ int rc;
+
+ rc = write(STDERR_FILENO, fmt + nwritten, ntotal - nwritten);
+
+ /* Just give up on error. There isn't much else we can do. */
+ if (rc == -1)
+ return;
+
+ nwritten += rc;
+ }
+}
+
+
/*
* Adjust the level of a recovery-related message per trace_recovery_messages.
*
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 4a9562fdaa..20f1b54c6a 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -529,11 +529,7 @@ extern void write_pipe_chunks(char *data, int len, int dest);
extern void write_csvlog(ErrorData *edata);
extern void write_jsonlog(ErrorData *edata);
-/*
- * Write errors to stderr (or by equal means when stderr is
- * not available). Used before ereport/elog can be used
- * safely (memory context, GUC load etc)
- */
extern void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
+extern void write_stderr_signal_safe(const char *fmt);
#endif /* ELOG_H */
--
2.25.1
v13-0003-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 484628f4ec14b3156105f142ab7adc15b160d357 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v13 3/7] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 46af349564..d94730c50c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..e780438948 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2610,6 +2610,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..7346a3f1ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v13-0004-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 6fc7ba367e58382675a9ba8ba0e5d18fdd86ccfd Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v13 4/7] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 246 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 408 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index e4bf0fe9c0..23812e9a6a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -20,7 +20,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 94e152694e..b3e1c18c65 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 543d4d897a..8e1e71c256 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -692,6 +693,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4890,12 +4892,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7310,12 +7318,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -8892,6 +8906,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 41684418b6..4b45ea8753 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration of
- * the system() call because there isn't a good way to break out while it
- * is executing. Since we might call proc_exit() in a signal handler, it
- * is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index dbe9394762..f0e1007f92 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4106,7 +4106,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index ccfc382fcf..bb3ddfd38b 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -26,6 +26,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 0e7de26bc2..adce0ffcef 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -198,8 +198,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -223,6 +222,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -298,8 +307,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..2b27cfced0
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..fcd5475c88
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,246 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index 56576dcf5c..bbc8080845 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..b9bd0a93cd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index 33c0a5562c..abaa529462 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -149,6 +149,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 6a2e4c4526..09d3f89c69 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..570588e493
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9303bf5..acdc29c0ef 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -132,6 +132,7 @@ ArchiveModuleState
ArchiveOpts
ArchiveShutdownCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v13-0005-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From 991730cbdad3e4d512f7ef8d99419063f56669a8 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v13 5/7] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 0d6be9a2fa..66a4de4762 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f1ff6bb451 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v13-0006-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 4c4688d3988ac080ad32ad8eb4d2bd7c3c04ad72 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v13 6/7] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 207 ++++++++++--------
1 file changed, 111 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7cf44e82e2..f30ee591e7 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,53 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know how to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +73,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has a state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has a state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v13-0007-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 8fcd67fb5908779a6e32aae7fc78110ff3499f51 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v13 7/7] introduce restore_library
---
contrib/basic_archive/Makefile | 1 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 28 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 53 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 142 +++++++
src/include/restore/shell_restore.h | 16 +-
19 files changed, 1103 insertions(+), 88 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..d12d45e0d2 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -5,6 +5,7 @@ PGFILEDESC = "basic_archive - basic archive module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
+TAP_TESTS = 1
# Disabled because these tests require "shared_preload_libraries=basic_archive",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index cd852888ce..b04d83da4c 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..4e9f5002de 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..85b14c5113
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL was replayed
+my $result = $restore->safe_psql("postgres", "SELECT count(*) FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index f30ee591e7..56373a211b 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -167,4 +169,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..5555f23a85 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1218,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1255,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c41cc6c6..288d58f48a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3807,7 +3807,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3835,7 +3836,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3870,7 +3872,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3915,7 +3952,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3944,7 +3984,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 9d0deaeeb8..094140cecc 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e1e71c256..f06385bea5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -4891,18 +4891,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7317,18 +7318,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b45ea8753..50bcda1120 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index f0e1007f92..c6645c2413 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1078,18 +1079,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index aaad5c5228..73ff592f9f 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -233,6 +234,24 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback, if
+ * one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking checkpoints
+ * or shutting down the server when the parameters are misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -548,6 +567,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +586,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides restarting
+ * the process, and there should be little harm in leaving it around,
+ * so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index adce0ffcef..a096b79182 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -29,6 +29,7 @@
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -148,13 +149,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -176,6 +181,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded shutdown
+ * callback and load the new callbacks. There's presently no good way to
+ * unload a library besides restarting the process, and there should be
+ * little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -285,6 +314,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index fcd5475c88..b67cd4ac48 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2023, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -153,8 +206,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -162,8 +215,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -173,8 +227,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -182,8 +236,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..a42819399b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -63,6 +63,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3788,6 +3789,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..53757fea5f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -270,6 +270,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..c0aef33ba5
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,142 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence merely
+ * copies the file. If set to true, the server will verify that the
+ * timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 570588e493..1eeae51d81 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
--
2.25.1
I noticed that the new TAP test for basic_archive was failing
intermittently for cfbot. It looks like the query for checking that the
post-backup WAL is restored sometimes executes before archive recovery is
complete (because hot_standby is on). To fix this, I adjusted the test to
use poll_query_until instead. There are no other changes in v14.
I first tried to set hot_standby to off on the restored node so that the
query wouldn't run until archive recovery completed. This seemed like it
would work because start() useѕ "pg_ctl --wait", which has the following
note in the docs:
Startup is considered complete when the PID file indicates that the
server is ready to accept connections.
However, that's not what happens when hot_standby is off. In that case,
the postmaster.pid file is updated with PM_STATUS_STANDBY once recovery
starts, which wait_for_postmaster_start() interprets as "ready." I see
this was reported before [0]/messages/by-id/CAMkU=1wrMqPggnEfszE-c3PPLmKgRK17_qr7tmxBECYEbyV-4Q@mail.gmail.com, but that discussion fizzled out. IIUC it was
done this way to avoid infinite waits when hot_standby is off and standby
mode is enabled. I could be missing something obvious, but that doesn't
seem necessary when hot_standby is off and recovery mode is enabled because
recovery should end at some point (never mind the halting problem). I'm
still digging into this and may spin off a new thread if I can conjure up a
proposal.
[0]: /messages/by-id/CAMkU=1wrMqPggnEfszE-c3PPLmKgRK17_qr7tmxBECYEbyV-4Q@mail.gmail.com
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v14-0001-Move-extra-code-out-of-the-Pre-PostRestoreComman.patchtext/x-diff; charset=us-asciiDownload
From ef9aade7270d12104647439a99e3b1822393a318 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 23 Feb 2023 14:27:48 -0800
Subject: [PATCH v14 1/7] Move extra code out of the Pre/PostRestoreCommand()
block.
If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section. This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block. This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index fcc87ff44f..41684418b6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
(errmsg_internal("executing restore command \"%s\"",
xlogRestoreCmd)));
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
/*
- * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
PostRestoreCommand();
+
+ pgstat_report_wait_end();
pfree(xlogRestoreCmd);
if (rc == 0)
--
2.25.1
v14-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patchtext/x-diff; charset=us-asciiDownload
From 78fdf06e4b1bbb37c8ae37937728aaeb4b2cf50b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 14 Feb 2023 09:44:53 -0800
Subject: [PATCH v14 2/7] Don't proc_exit() in startup's SIGTERM handler if
forked by system().
Instead, emit a message to STDERR and _exit() in this case. This
change also adds assertions to proc_exit(), ProcKill(), and
AuxiliaryProcKill() to verify that these functions are not called
by a process forked by system(), etc.
---
src/backend/postmaster/startup.c | 17 ++++++++++++++++-
src/backend/storage/ipc/ipc.c | 3 +++
src/backend/storage/lmgr/proc.c | 2 ++
src/backend/utils/error/elog.c | 28 ++++++++++++++++++++++++++++
src/include/utils/elog.h | 6 +-----
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index efc2580536..0e7de26bc2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,6 +19,8 @@
*/
#include "postgres.h"
+#include <unistd.h>
+
#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
@@ -121,7 +123,20 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
int save_errno = errno;
if (in_restore_command)
- proc_exit(1);
+ {
+ /*
+ * If we are in a child process (e.g., forked by system() in
+ * RestoreArchivedFile()), we don't want to call any exit callbacks.
+ * The parent will take care of that.
+ */
+ if (MyProcPid == (int) getpid())
+ proc_exit(1);
+ else
+ {
+ write_stderr_signal_safe("StartupProcShutdownHandler() called in child process\n");
+ _exit(1);
+ }
+ }
else
shutdown_requested = true;
WakeupRecovery();
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 1904d21795..d5097dc008 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -103,6 +103,9 @@ static int on_proc_exit_index,
void
proc_exit(int code)
{
+ /* proc_exit() is not safe in forked processes from system(), etc. */
+ Assert(MyProcPid == (int) getpid());
+
/* Clean up everything that must be cleaned up */
proc_exit_prepare(code);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 22b4278610..b9e2c3aafe 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -805,6 +805,7 @@ ProcKill(int code, Datum arg)
dlist_head *procgloballist;
Assert(MyProc != NULL);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
/* Make sure we're out of the sync rep lists */
SyncRepCleanupAtProcExit();
@@ -925,6 +926,7 @@ AuxiliaryProcKill(int code, Datum arg)
PGPROC *proc;
Assert(proctype >= 0 && proctype < NUM_AUXILIARY_PROCS);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
auxproc = &AuxiliaryProcs[proctype];
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 5898100acb..f9925c8d8e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -3730,6 +3730,34 @@ write_stderr(const char *fmt,...)
}
+/*
+ * Write a message to STDERR using only async-signal-safe functions. This can
+ * be used to safely emit a message from a signal handler.
+ *
+ * TODO: It is likely possible to safely do a limited amount of string
+ * interpolation (e.g., %s and %d), but that is not presently supported.
+ */
+void
+write_stderr_signal_safe(const char *fmt)
+{
+ int nwritten = 0;
+ int ntotal = strlen(fmt);
+
+ while (nwritten < ntotal)
+ {
+ int rc;
+
+ rc = write(STDERR_FILENO, fmt + nwritten, ntotal - nwritten);
+
+ /* Just give up on error. There isn't much else we can do. */
+ if (rc == -1)
+ return;
+
+ nwritten += rc;
+ }
+}
+
+
/*
* Adjust the level of a recovery-related message per trace_recovery_messages.
*
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 4a9562fdaa..20f1b54c6a 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -529,11 +529,7 @@ extern void write_pipe_chunks(char *data, int len, int dest);
extern void write_csvlog(ErrorData *edata);
extern void write_jsonlog(ErrorData *edata);
-/*
- * Write errors to stderr (or by equal means when stderr is
- * not available). Used before ereport/elog can be used
- * safely (memory context, GUC load etc)
- */
extern void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
+extern void write_stderr_signal_safe(const char *fmt);
#endif /* ELOG_H */
--
2.25.1
v14-0003-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 3d848cad6842f3079aff56b79b0243fdaf82b441 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v14 3/7] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 46af349564..d94730c50c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 51e07d5582..e780438948 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2610,6 +2610,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..7346a3f1ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v14-0004-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 663635734c33717868035d3c0d075e5f538a0cc7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v14 4/7] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 246 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 408 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index e4bf0fe9c0..23812e9a6a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -20,7 +20,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 94e152694e..b3e1c18c65 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 543d4d897a..8e1e71c256 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -692,6 +693,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4890,12 +4892,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7310,12 +7318,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -8892,6 +8906,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 41684418b6..4b45ea8753 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration of
- * the system() call because there isn't a good way to break out while it
- * is executing. Since we might call proc_exit() in a signal handler, it
- * is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index dbe9394762..f0e1007f92 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4106,7 +4106,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index ccfc382fcf..bb3ddfd38b 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -26,6 +26,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 0e7de26bc2..adce0ffcef 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -198,8 +198,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -223,6 +222,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -298,8 +307,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..2b27cfced0
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..fcd5475c88
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,246 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index 56576dcf5c..bbc8080845 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..b9bd0a93cd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index 33c0a5562c..abaa529462 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -149,6 +149,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 6a2e4c4526..09d3f89c69 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..570588e493
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 86a9303bf5..acdc29c0ef 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -132,6 +132,7 @@ ArchiveModuleState
ArchiveOpts
ArchiveShutdownCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v14-0005-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From d9be9fb7a8ceea84cbc6987a37ff84425890ea98 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v14 5/7] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 0d6be9a2fa..66a4de4762 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f1ff6bb451 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v14-0006-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From b9c6a55baf8185dc19df57c89390608e47d785f7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v14 6/7] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 207 ++++++++++--------
1 file changed, 111 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7cf44e82e2..f30ee591e7 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,53 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know how to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the archive module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +73,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has a state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has a state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has state, this callback should
+ free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v14-0007-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From c62059262097ce3f8626dbdacdc3cc0ee56b2e2f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v14 7/7] introduce restore_library
---
contrib/basic_archive/Makefile | 1 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 28 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 53 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 142 +++++++
src/include/restore/shell_restore.h | 16 +-
19 files changed, 1103 insertions(+), 88 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..d12d45e0d2 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -5,6 +5,7 @@ PGFILEDESC = "basic_archive - basic archive module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
+TAP_TESTS = 1
# Disabled because these tests require "shared_preload_libraries=basic_archive",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index cd852888ce..b04d83da4c 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..4e9f5002de 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..f7fa124695
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index f30ee591e7..56373a211b 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -167,4 +169,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index be05a33205..5555f23a85 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1218,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1255,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e5c41cc6c6..288d58f48a 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3807,7 +3807,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3835,7 +3836,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3870,7 +3872,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3915,7 +3952,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3944,7 +3984,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 9d0deaeeb8..094140cecc 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e1e71c256..f06385bea5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -4891,18 +4891,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7317,18 +7318,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 4b45ea8753..50bcda1120 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index f0e1007f92..c6645c2413 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1078,18 +1079,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index aaad5c5228..73ff592f9f 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -233,6 +234,24 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback, if
+ * one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking checkpoints
+ * or shutting down the server when the parameters are misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -548,6 +567,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -563,6 +586,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides restarting
+ * the process, and there should be little harm in leaving it around,
+ * so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index adce0ffcef..a096b79182 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -29,6 +29,7 @@
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -148,13 +149,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -176,6 +181,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded shutdown
+ * callback and load the new callbacks. There's presently no good way to
+ * unload a library besides restarting the process, and there should be
+ * little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -285,6 +314,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index fcd5475c88..b67cd4ac48 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2023, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -153,8 +206,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -162,8 +215,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -173,8 +227,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -182,8 +236,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1c0583fe26..a42819399b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -63,6 +63,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3788,6 +3789,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d06074b86f..53757fea5f 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -270,6 +270,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..c0aef33ba5
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,142 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence merely
+ * copies the file. If set to true, the server will verify that the
+ * timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 570588e493..1eeae51d81 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
--
2.25.1
On Tue, Mar 14, 2023 at 09:13:09PM -0700, Nathan Bossart wrote:
However, that's not what happens when hot_standby is off. In that case,
the postmaster.pid file is updated with PM_STATUS_STANDBY once recovery
starts, which wait_for_postmaster_start() interprets as "ready." I see
this was reported before [0], but that discussion fizzled out. IIUC it was
done this way to avoid infinite waits when hot_standby is off and standby
mode is enabled. I could be missing something obvious, but that doesn't
seem necessary when hot_standby is off and recovery mode is enabled because
recovery should end at some point (never mind the halting problem). I'm
still digging into this and may spin off a new thread if I can conjure up a
proposal.[0] /messages/by-id/CAMkU=1wrMqPggnEfszE-c3PPLmKgRK17_qr7tmxBECYEbyV-4Q@mail.gmail.com
These days, knowing hot_standby is on by default, and that users would
recover up to the end-of-backup record of just use read replicas, do
we have a strong case for keeping this GUC parameter at all? It does
not strike me that we really need to change a five-year-old behavior
if there has been few complaints about it. I agree that it is
confusing as it stands, but the long-term simplifications may be worth
it in the recovery code (aka less booleans needed to track the flow of
the startup process, and less confusion around that).
--
Michael
On Fri, Feb 17, 2023 at 11:41:32AM -0800, Andres Freund wrote:
I don't fully now, it's not entirely clear to me what the goals here were. I
think you'd likely need to do a bit of infrastructure work to do this
sanely. So far we just didn't have the need to handle files being released in
a way like you want to do there.I suspect a good direction would be to use resource owners. Add a separate set
of functions that release files on resource owner release. Most of the
infrastructure is there already, for temporary files
(c.f. OpenTemporaryFile()).
Yes, perhaps. I've had good experience with these when it comes to
avoid leakages when releasing resources, particularly for resources
allocated by external libraries (cough, OpenSSL, cough). And there
was some work to make these more scalable, for example.
At this stage of the CF, it seems pretty clear to me that this should
be pushed to v17, so moved to next CF.
--
Michael
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v15-0001-Move-extra-code-out-of-the-Pre-PostRestoreComman.patchtext/x-diff; charset=us-asciiDownload
From b8856e61d775bc248a292163facc0b227abdde97 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Thu, 23 Feb 2023 14:27:48 -0800
Subject: [PATCH v15 1/7] Move extra code out of the Pre/PostRestoreCommand()
block.
If SIGTERM is received within this block, the startup process will
immediately proc_exit() in the signal handler, so it is inadvisable
to include any more code than is required in this section. This
change moves the code recently added to this block (see 1b06d7b and
7fed801) to outside of the block. This ensures that only system()
is called while proc_exit() might be called in the SIGTERM handler,
which is how this code worked from v8.4 to v14.
---
src/backend/access/transam/xlogarchive.c | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index f3fb92c8f9..b998cd651c 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -159,20 +159,27 @@ RestoreArchivedFile(char *path, const char *xlogfname,
(errmsg_internal("executing restore command \"%s\"",
xlogRestoreCmd)));
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
/*
- * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
*/
PreRestoreCommand();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
rc = system(xlogRestoreCmd);
- pgstat_report_wait_end();
PostRestoreCommand();
+
+ pgstat_report_wait_end();
pfree(xlogRestoreCmd);
if (rc == 0)
--
2.25.1
v15-0002-Don-t-proc_exit-in-startup-s-SIGTERM-handler-if-.patchtext/x-diff; charset=us-asciiDownload
From e8105f300a2e141c8b5db478a16bc29e029b30df Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Tue, 14 Feb 2023 09:44:53 -0800
Subject: [PATCH v15 2/7] Don't proc_exit() in startup's SIGTERM handler if
forked by system().
Instead, emit a message to STDERR and _exit() in this case. This
change also adds assertions to proc_exit(), ProcKill(), and
AuxiliaryProcKill() to verify that these functions are not called
by a process forked by system(), etc.
---
src/backend/postmaster/startup.c | 17 ++++++++++++++++-
src/backend/storage/ipc/ipc.c | 3 +++
src/backend/storage/lmgr/proc.c | 2 ++
src/backend/utils/error/elog.c | 28 ++++++++++++++++++++++++++++
src/include/utils/elog.h | 6 +-----
5 files changed, 50 insertions(+), 6 deletions(-)
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index efc2580536..0e7de26bc2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -19,6 +19,8 @@
*/
#include "postgres.h"
+#include <unistd.h>
+
#include "access/xlog.h"
#include "access/xlogrecovery.h"
#include "access/xlogutils.h"
@@ -121,7 +123,20 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
int save_errno = errno;
if (in_restore_command)
- proc_exit(1);
+ {
+ /*
+ * If we are in a child process (e.g., forked by system() in
+ * RestoreArchivedFile()), we don't want to call any exit callbacks.
+ * The parent will take care of that.
+ */
+ if (MyProcPid == (int) getpid())
+ proc_exit(1);
+ else
+ {
+ write_stderr_signal_safe("StartupProcShutdownHandler() called in child process\n");
+ _exit(1);
+ }
+ }
else
shutdown_requested = true;
WakeupRecovery();
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 1904d21795..d5097dc008 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -103,6 +103,9 @@ static int on_proc_exit_index,
void
proc_exit(int code)
{
+ /* proc_exit() is not safe in forked processes from system(), etc. */
+ Assert(MyProcPid == (int) getpid());
+
/* Clean up everything that must be cleaned up */
proc_exit_prepare(code);
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 22b4278610..b9e2c3aafe 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -805,6 +805,7 @@ ProcKill(int code, Datum arg)
dlist_head *procgloballist;
Assert(MyProc != NULL);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
/* Make sure we're out of the sync rep lists */
SyncRepCleanupAtProcExit();
@@ -925,6 +926,7 @@ AuxiliaryProcKill(int code, Datum arg)
PGPROC *proc;
Assert(proctype >= 0 && proctype < NUM_AUXILIARY_PROCS);
+ Assert(MyProc->pid == (int) getpid()); /* not safe if forked by system(), etc. */
auxproc = &AuxiliaryProcs[proctype];
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 5898100acb..f9925c8d8e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -3730,6 +3730,34 @@ write_stderr(const char *fmt,...)
}
+/*
+ * Write a message to STDERR using only async-signal-safe functions. This can
+ * be used to safely emit a message from a signal handler.
+ *
+ * TODO: It is likely possible to safely do a limited amount of string
+ * interpolation (e.g., %s and %d), but that is not presently supported.
+ */
+void
+write_stderr_signal_safe(const char *fmt)
+{
+ int nwritten = 0;
+ int ntotal = strlen(fmt);
+
+ while (nwritten < ntotal)
+ {
+ int rc;
+
+ rc = write(STDERR_FILENO, fmt + nwritten, ntotal - nwritten);
+
+ /* Just give up on error. There isn't much else we can do. */
+ if (rc == -1)
+ return;
+
+ nwritten += rc;
+ }
+}
+
+
/*
* Adjust the level of a recovery-related message per trace_recovery_messages.
*
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 4a9562fdaa..20f1b54c6a 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -529,11 +529,7 @@ extern void write_pipe_chunks(char *data, int len, int dest);
extern void write_csvlog(ErrorData *edata);
extern void write_jsonlog(ErrorData *edata);
-/*
- * Write errors to stderr (or by equal means when stderr is
- * not available). Used before ereport/elog can be used
- * safely (memory context, GUC load etc)
- */
extern void write_stderr(const char *fmt,...) pg_attribute_printf(1, 2);
+extern void write_stderr_signal_safe(const char *fmt);
#endif /* ELOG_H */
--
2.25.1
v15-0003-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 5cdf8bc04f1e71b350a665a3da1bb31a172f6a3a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v15 3/7] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 46af349564..d94730c50c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7d3b20168a..828c7ff074 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2609,6 +2609,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index ba89d013e6..7346a3f1ea 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v15-0004-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From aae35e58a5abf6f9c3272d4b8ba7f121ef0bdeb1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v15 4/7] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 246 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 408 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index e4bf0fe9c0..23812e9a6a 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -20,7 +20,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 94e152694e..b3e1c18c65 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b540ee293b..eaba35b59e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -692,6 +693,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4903,12 +4905,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7327,12 +7335,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during log
+ * shipping replication. All files earlier than this point can be deleted
+ * from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -8915,6 +8929,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index b998cd651c..a925ac22f6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration of
- * the system() call because there isn't a good way to break out while it
- * is executing. Since we might call proc_exit() in a signal handler, it
- * is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 02d1b2cd6d..e3b02ed760 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4125,7 +4125,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index ccfc382fcf..bb3ddfd38b 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -26,6 +26,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 0e7de26bc2..adce0ffcef 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -198,8 +198,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -223,6 +222,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -298,8 +307,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..2b27cfced0
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..fcd5475c88
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,246 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * Check signals before restore command and reset afterwards.
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration of
+ * the system() call because there isn't a good way to break out while it
+ * is executing. Since we might call proc_exit() in a signal handler, it
+ * is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index 56576dcf5c..bbc8080845 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..b9bd0a93cd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index 33c0a5562c..abaa529462 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -149,6 +149,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 6a2e4c4526..09d3f89c69 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..570588e493
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b4058b88c3..fd0dc181ef 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -133,6 +133,7 @@ ArchiveModuleState
ArchiveOpts
ArchiveShutdownCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v15-0005-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From 4455eac640ef2099cdb6212d1109940a139b0ae1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v15 5/7] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 0d6be9a2fa..66a4de4762 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -100,7 +100,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2e271862fc..f1ff6bb451 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v15-0006-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 3c8a2b7d94b1bb523cd8657cfa6fc5ad01d559c8 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v15 6/7] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 206 ++++++++++--------
1 file changed, 110 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7064307d9e..7ed50a3b52 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +72,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v15-0007-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From acfa0453eb0f20e586f9c27b80a4632f9311be24 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v15 7/7] introduce restore_library
---
contrib/basic_archive/Makefile | 1 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 28 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 53 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 142 +++++++
src/include/restore/shell_restore.h | 16 +-
19 files changed, 1103 insertions(+), 88 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..d12d45e0d2 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -5,6 +5,7 @@ PGFILEDESC = "basic_archive - basic archive module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
+TAP_TESTS = 1
# Disabled because these tests require "shared_preload_libraries=basic_archive",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index cd852888ce..b04d83da4c 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..4e9f5002de 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..f7fa124695
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7ed50a3b52..bf2fdb313a 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -166,4 +168,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8cb24d6ae5..4b20651234 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1218,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1255,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 091a79d4f3..4b0dd21108 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3873,7 +3873,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3901,7 +3902,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3936,7 +3938,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3981,7 +4018,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4010,7 +4050,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 9d0deaeeb8..094140cecc 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index eaba35b59e..79d33e565a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -4904,18 +4904,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7334,18 +7335,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during log
- * shipping replication. All files earlier than this point can be deleted
- * from the archive, though there is no requirement to do so.
+ * The callback is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index a925ac22f6..b1354dcee9 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index e3b02ed760..9153e7bac0 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1078,18 +1079,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index ace9893d95..403f4051c0 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -233,6 +234,24 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback, if
+ * one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking checkpoints
+ * or shutting down the server when the parameters are misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -547,6 +566,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -562,6 +585,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides restarting
+ * the process, and there should be little harm in leaving it around,
+ * so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index adce0ffcef..a096b79182 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -29,6 +29,7 @@
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -148,13 +149,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -176,6 +181,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded shutdown
+ * callback and load the new callbacks. There's presently no good way to
+ * unload a library besides restarting the process, and there should be
+ * little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -285,6 +314,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index fcd5475c88..b67cd4ac48 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2023, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -153,8 +206,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -162,8 +215,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -173,8 +227,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -182,8 +236,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index cab3ddbe11..81e4d6d183 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -65,6 +65,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3839,6 +3840,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dce5049bc2..5b96fe5ea8 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -275,6 +275,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..c0aef33ba5
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,142 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence merely
+ * copies the file. If set to true, the server will verify that the
+ * timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 570588e493..1eeae51d81 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
--
2.25.1
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v16-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 0934f23773a612051c36070ed1f3b8ab100c4c65 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v16 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 46af349564..d94730c50c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 39d3775e80..847b62e161 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2638,6 +2638,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 902e57c0ca..e4eef0da81 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -372,6 +372,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v16-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 7f6a8d792620b4d5ae097eb43f25be69f89f17c6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v16 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3e275ac759..6913601c36 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 94e152694e..b3e1c18c65 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 40461923ea..1307748b4e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -702,6 +703,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -4982,12 +4984,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7446,12 +7454,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9067,6 +9081,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 524e80adb1..a925ac22f6 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 315e4b27cb..d43c229eca 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4142,7 +4142,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 88a35e9676..0381bae024 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 0e7de26bc2..adce0ffcef 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -198,8 +198,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -223,6 +222,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -298,8 +307,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..2b27cfced0
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..bc1f4a431a
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index a7ca277803..3d276d55aa 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 31ff206034..b9bd0a93cd 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index d50897c9fd..849e1e4dbf 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 6a2e4c4526..09d3f89c69 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..570588e493
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 06b25617bc..16220cfe44 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -134,6 +134,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v16-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From 1b382c3432af083c8bf3553bf699b0ddab5d41ad Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v16 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 4c63a7e768..b175152e6b 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index f629524be0..b12a9f8dd5 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -233,7 +233,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v16-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From e6e4fde1121ccf03a61d8a26cf96926415d737a4 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v16 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 206 ++++++++++--------
1 file changed, 110 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7064307d9e..7ed50a3b52 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +72,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v16-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 2c4aa184387fa74e51e1511841dd0871fcee73b7 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v16 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 1 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1103 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 55d299d650..d12d45e0d2 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -5,6 +5,7 @@ PGFILEDESC = "basic_archive - basic archive module"
REGRESS = basic_archive
REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.conf
+TAP_TESTS = 1
# Disabled because these tests require "shared_preload_libraries=basic_archive",
# which typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 4d78c31859..862689f981 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2023, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index bc1380e6f6..4e9f5002de 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# which typical runningcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..f7fa124695
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2023, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7ed50a3b52..bf2fdb313a 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -166,4 +168,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 8cb24d6ae5..4b20651234 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1180,9 +1180,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1201,14 +1218,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1232,7 +1255,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 3839c72c86..cdc2f902e0 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3751,7 +3751,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3779,7 +3780,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3814,7 +3816,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3859,7 +3896,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3888,7 +3928,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 5f9257313a..09705a14a2 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1307748b4e..c79547e403 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -4983,18 +4983,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7453,18 +7454,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index a925ac22f6..316b866af0 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index d43c229eca..8d231dcc82 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1077,18 +1078,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index ace9893d95..4ded7f3ff3 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -233,6 +234,25 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -547,6 +567,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -562,6 +586,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index adce0ffcef..8ad570c725 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -29,6 +29,7 @@
#include "pgstat.h"
#include "postmaster/interrupt.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -148,13 +149,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -176,6 +181,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -285,6 +314,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index bc1f4a431a..ce77fde66b 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2023, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 4c58574166..21e91908aa 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -67,6 +67,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3828,6 +3829,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index d08d55c3fe..2f9ad7e1bd 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -272,6 +272,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..b9a5810aa8
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2023, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 570588e493..1eeae51d81 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 16220cfe44..c70e15aba7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2362,6 +2362,8 @@ ResourceOwner
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v17-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 6cee7d220b886e9be309929da5274c5770e59845 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v17 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 67693b0580..6ae8bb53c4 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -824,11 +824,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 8f65ef3d89..9d21c16169 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2640,6 +2640,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 471d53da8f..6543e61463 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -375,6 +375,9 @@ extern int NewGUCNestLevel(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v17-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 9f5c2602baa8e29058bc976c01516d04e3c1f901 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v17 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 7d2ea7d54a..cbeb8ac93c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 478377c4a2..957b406272 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -85,6 +85,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -703,6 +704,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5081,12 +5083,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7564,12 +7572,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9199,6 +9213,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 1292b11f2e..d2d4a38a0c 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
@@ -54,12 +54,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -73,8 +72,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -150,39 +162,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -238,37 +244,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -282,74 +257,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 1b48d7171a..be5edf4beb 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4157,7 +4157,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 8767aaba67..719c14bc1a 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index d53c37d062..eaa7e0451d 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -197,8 +197,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -222,6 +221,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -297,8 +306,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index 11dd6f0d77..528c5a804d 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index cf7a43e38c..d2662fa58a 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 5fd46b7bd1..2f33dff7e5 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -135,6 +135,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v17-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From b975836929b477ae6bdf8d4673602538869c9f96 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v17 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index bb4926b887..89bc9c689a 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2c107199d3..23522ccaf3 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -228,7 +228,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v17-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From d16feb7b409162600c931c52db4ccfacd826cdea Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v17 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 206 ++++++++++--------
1 file changed, 110 insertions(+), 96 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7064307d9e..7ed50a3b52 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,91 +72,98 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
- </sect2>
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
+ </para>
+ </sect3>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
-
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v17-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From df6bfcfcf0df3bd3b26ab216a6f25072d6aab0ab Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v17 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 804567e919..00d7f1470e 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -426,3 +468,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 7ed50a3b52..bf2fdb313a 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -166,4 +168,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index b3468eea3c..e1643f92e0 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1261,9 +1261,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1282,14 +1299,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1313,7 +1336,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 61038472c5..290903c6f5 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3767,7 +3767,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3795,7 +3796,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3830,7 +3832,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -3875,7 +3912,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -3904,7 +3944,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 9dd52ff275..96817fdc78 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 957b406272..a6c59307b0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -85,7 +85,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5082,18 +5082,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7571,18 +7572,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index d2d4a38a0c..4b321878e9 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,15 +23,22 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -75,15 +82,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -175,14 +182,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -190,6 +200,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -631,3 +651,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index be5edf4beb..d2f5e39d09 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -50,6 +50,7 @@
#include "postmaster/startup.h"
#include "replication/slot.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1088,18 +1089,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 5e949fc885..557fc496fe 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -224,6 +225,25 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -561,6 +581,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -576,6 +600,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index eaa7e0451d..de18a6db1a 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -28,6 +28,7 @@
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/latch.h"
#include "storage/pmsignal.h"
@@ -147,13 +148,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -175,6 +180,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -284,6 +313,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index e53ebc6dc2..7b1ea733ee 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -68,6 +68,7 @@
#include "replication/logicallauncher.h"
#include "replication/slot.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3884,6 +3885,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 835b0e9ba8..99de17a08e 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -275,6 +275,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2f33dff7e5..4a756c92d8 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2378,6 +2378,8 @@ ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
Here is another rebase. Given the size of this patch set and the lack of
review, I am going to punt this one to v18.
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v18-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From dd69e5987b477818f60b202af5fb9b2603dc8acb Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v18 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index f97035ca03..dcb5222bbe 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -815,11 +815,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dd5a46469a..b5c8e3702a 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2660,6 +2660,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 3712aba09b..3088ada610 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v18-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 83a71392a83b7d04d50568e7c5466630528503a5 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v18 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index d66e2a4b9f..be00835995 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 20a5f86209..e5d12f1d15 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -698,6 +699,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5177,12 +5179,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7666,12 +7674,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9301,6 +9315,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 977a2fe06b..320fa857ea 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 853b540945..0bfa569502 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4206,7 +4206,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8b51e45bad..e391bf6aa6 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -168,8 +168,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -193,6 +192,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -268,8 +277,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index 584d0300d9..2a72113e2e 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index cf7a43e38c..d2662fa58a 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 95ae7845d8..3a02310f26 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v18-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From fd719e1bfc2671e4b867829496e7af813ff15c0e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v18 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index e0dca81cb2..31a57943f7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2c107199d3..23522ccaf3 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -228,7 +228,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v18-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 3c9db963a508bedb5f170543a3d5a5ed7ec536de Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v18 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 226 ++++++++++--------
1 file changed, 120 insertions(+), 106 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index cf7438a759..a52d15082d 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,103 +72,110 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v18-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 296ae059c9380ddefd96d93b4f83ea2694a484d9 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v18 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 6b102e9072..e0d8dce364 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -431,3 +473,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index a52d15082d..15be548a73 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -178,4 +180,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index b3468eea3c..e1643f92e0 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1261,9 +1261,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1282,14 +1299,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1313,7 +1336,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index b38cbd714a..bba79e19ab 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3906,7 +3906,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3934,7 +3935,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3969,7 +3971,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4014,7 +4051,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4043,7 +4083,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index 236c0af65f..f811398fa2 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e5d12f1d15..34a21c40a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5178,18 +5178,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7673,18 +7674,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 320fa857ea..8ebbd6ce50 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/startup.h"
#include "postmaster/pgarch.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 0bfa569502..4afdcc3aad 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1114,18 +1115,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 46197d56f8..47993aa35e 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -224,6 +225,25 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -556,6 +576,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -571,6 +595,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index e391bf6aa6..3cbd382e00 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -25,6 +25,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -118,13 +119,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -146,6 +151,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -255,6 +284,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 45013582a7..724d268d7b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3946,6 +3947,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index edcc0282b2..2939042b06 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -284,6 +284,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3a02310f26..6a05313f6b 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2386,6 +2386,8 @@ ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
rebased for cfbot
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v19-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From d3f497906daf1c405059b2c292f1eeb5cfeb742b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v19 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index f97035ca03..dcb5222bbe 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -815,11 +815,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 391866145e..ac507008ce 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 3712aba09b..3088ada610 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v19-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From a8e50ef80c91927d388b23b6e9eb034e046c02af Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v19 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 3d7be09529..88e0e53f82 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 20a5f86209..e5d12f1d15 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -698,6 +699,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5177,12 +5179,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7666,12 +7674,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9301,6 +9315,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index caa1f03d93..5756f52124 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 29c5bec084..07213b1897 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4209,7 +4209,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 8b51e45bad..e391bf6aa6 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -168,8 +168,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -193,6 +192,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -268,8 +277,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index cf7a43e38c..d2662fa58a 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(void) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index aa7a25b8f8..2f975ac53a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v19-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From cd5ea7d1b1cb5d37eef8bdc21a8305249423c138 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v19 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index e0dca81cb2..31a57943f7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2c107199d3..23522ccaf3 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -228,7 +228,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v19-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From d631331c112c36a920e2f4eafed2bd572b35d0b1 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v19 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 226 ++++++++++--------
1 file changed, 120 insertions(+), 106 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index cf7438a759..a52d15082d 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,103 +72,110 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v19-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 0f05e52278b66d3570ce5620dfe2903a0c6574c6 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v19 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 6b102e9072..e0d8dce364 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -431,3 +473,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index a52d15082d..15be548a73 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -178,4 +180,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index b3468eea3c..e1643f92e0 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1261,9 +1261,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1282,14 +1299,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1313,7 +1336,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 65a6e6c408..9a2570f87f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3906,7 +3906,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3934,7 +3935,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3969,7 +3971,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4014,7 +4051,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4043,7 +4083,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e5d12f1d15..34a21c40a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5178,18 +5178,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7673,18 +7674,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 5756f52124..3a05581495 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 07213b1897..1bf0d0ea11 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1117,18 +1118,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 46197d56f8..47993aa35e 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,6 +45,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -224,6 +225,25 @@ CheckpointerMain(void)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -556,6 +576,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -571,6 +595,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index e391bf6aa6..3cbd382e00 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -25,6 +25,7 @@
#include "libpq/pqsignal.h"
#include "miscadmin.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -118,13 +119,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -146,6 +151,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -255,6 +284,19 @@ StartupProcessMain(void)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 57d9de4dd9..295cde9247 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3946,6 +3947,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2244ee52f7..5265857519 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -284,6 +284,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2f975ac53a..8e190a0c79 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2388,6 +2388,8 @@ ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
another rebase for cfbot
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v20-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 0ccdbb0a010830380e6ff4b7a052198d50f0680f Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v20 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index c266904b57..55e9a1796b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -821,11 +821,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 391866145e..ac507008ce 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 3712aba09b..3088ada610 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v20-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 9d36df03e0eece1a08b7e778e6d4c82b2228582a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v20 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 6700aec039..590b5002c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 20a5f86209..e5d12f1d15 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -698,6 +699,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5177,12 +5179,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7666,12 +7674,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9301,6 +9315,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index caa1f03d93..5756f52124 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 29c5bec084..07213b1897 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4209,7 +4209,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..cc665ce259 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,8 +169,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -194,6 +193,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -274,8 +283,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index dde7ebde88..6ba1e48c02 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index cfa9d5aaea..a40c59b2e0 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v20-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From a04b5b040882b4e53dcc9ba60ce4474b1da7cd12 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v20 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index e0dca81cb2..31a57943f7 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index 2c107199d3..23522ccaf3 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -228,7 +228,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v20-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 726811882ed8d047da185143dc14a299b58866df Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v20 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 226 ++++++++++--------
1 file changed, 120 insertions(+), 106 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index cf7438a759..a52d15082d 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,103 +72,110 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
- </sect2>
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned, the server
+ will keep the original WAL file and retry archiving later.
+ <replaceable>file</replaceable> will contain just the file name of the WAL
+ file to archive, while <replaceable>path</replaceable> contains the full
+ path of the WAL file (including the file name).
+ </para>
+ </sect3>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v20-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 407be11af91b11a18e49c530b08a1b4758f5e0fd Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v20 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 6b102e9072..e0d8dce364 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -54,6 +60,16 @@ static void basic_archive_file_internal(const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
static void basic_archive_shutdown(ArchiveModuleState *state);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = basic_archive_startup,
@@ -62,6 +78,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = basic_archive_shutdown
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -71,7 +102,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -93,6 +124,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* basic_archive_startup
*
@@ -431,3 +473,112 @@ basic_archive_shutdown(ArchiveModuleState *state)
pfree(data);
state->private_data = NULL;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index a52d15082d..15be548a73 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -178,4 +180,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index b3468eea3c..e1643f92e0 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1261,9 +1261,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1282,14 +1299,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1313,7 +1336,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 65a6e6c408..9a2570f87f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3906,7 +3906,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3934,7 +3935,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3969,7 +3971,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4014,7 +4051,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4043,7 +4083,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e5d12f1d15..34a21c40a5 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5178,18 +5178,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7673,18 +7674,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 5756f52124..3a05581495 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 07213b1897..1bf0d0ea11 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1117,18 +1118,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 8ef600ae72..c31d0f3bd2 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -46,6 +46,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -230,6 +231,25 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -562,6 +582,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -577,6 +601,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cc665ce259..f8d3d6b3d2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
#include "miscadmin.h"
#include "postmaster/auxprocess.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -119,13 +120,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -147,6 +152,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -261,6 +290,19 @@ StartupProcessMain(char *startup_data, size_t startup_data_len)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 1e71e7db4a..f84724a0ed 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3946,6 +3947,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2244ee52f7..5265857519 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -284,6 +284,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index a40c59b2e0..db4cc68a88 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2405,6 +2405,8 @@ ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v21-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 5897631d5f09032565d92d5b8547baf3d24eef87 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v21 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 251f75e91d..d951089a79 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -913,11 +913,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index f51b3e0b50..b0118a516b 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 8d1fe04078..129484819c 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -377,6 +377,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v21-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From a01d9bd2d6a93a16b38ed071066f6901dd968391 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v21 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 6700aec039..590b5002c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index e3fb26f5ab..f8d98f6c0f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,6 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -705,6 +706,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5258,12 +5260,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7760,12 +7768,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9395,6 +9409,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index caa1f03d93..5756f52124 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b2fe2d04cc..437ecb8fdb 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4220,7 +4220,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..cc665ce259 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,8 +169,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -194,6 +193,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -274,8 +283,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index dde7ebde88..6ba1e48c02 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c83417ce9d..27d788428a 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v21-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From 84c5e3c6e7a8136c1f147f326bf8f0119489dc1c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v21 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 3fb0709f5d..d05da5fcf1 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index e9a350234e..4f0fe9d013 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -227,7 +227,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v21-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From a038baa7f93bc896b3b55c60344a3b96116276e3 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v21 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 242 +++++++++---------
1 file changed, 128 insertions(+), 114 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 10ec96eae9..6e368290b9 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,112 +72,119 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned or an error is thrown, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
-
- <note>
- <para>
- The <function>archive_file_cb</function> callback is called in a
- short-lived memory context that will be reset between invocations. If you
- need longer-lived storage, create a memory context in the module's
- <function>startup_cb</function> callback.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned or an error is
+ thrown, the server will keep the original WAL file and retry archiving
+ later. <replaceable>file</replaceable> will contain just the file name of
+ the WAL file to archive, while <replaceable>path</replaceable> contains the
+ full path of the WAL file (including the file name).
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <note>
+ <para>
+ The <function>archive_file_cb</function> callback is called in a
+ short-lived memory context that will be reset between invocations. If you
+ need longer-lived storage, create a memory context in the module's
+ <function>startup_cb</function> callback.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v21-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 8dc8cadb3f644541120801f26d6afa6d88f0415c Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v21 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 028cf51c25..e84bad27d2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -46,6 +52,16 @@ static bool basic_archive_configured(ArchiveModuleState *state);
static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = NULL,
@@ -54,6 +70,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = NULL
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -63,7 +94,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -85,6 +116,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* check_archive_directory
*
@@ -307,3 +349,112 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 6e368290b9..bcfa88dee0 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -187,4 +189,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index b3468eea3c..e1643f92e0 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1261,9 +1261,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1282,14 +1299,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1313,7 +1336,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index d8e1282e12..fafc7c359b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3909,7 +3909,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3937,7 +3938,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3972,7 +3974,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4017,7 +4054,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4046,7 +4086,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f8d98f6c0f..6635fc5f2f 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,7 +84,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5259,18 +5259,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7767,18 +7768,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 5756f52124..3a05581495 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 437ecb8fdb..a0e649cffc 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -52,6 +52,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1118,18 +1119,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 8ef600ae72..c31d0f3bd2 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -46,6 +46,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -230,6 +231,25 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -562,6 +582,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -577,6 +601,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cc665ce259..f8d3d6b3d2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
#include "miscadmin.h"
#include "postmaster/auxprocess.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -119,13 +120,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -147,6 +152,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -261,6 +290,19 @@ StartupProcessMain(char *startup_data, size_t startup_data_len)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index c68fdc008b..edb9792f43 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3988,6 +3989,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 2166ea4a87..ee2522ef46 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -285,6 +285,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 27d788428a..ff1e8e920c 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2439,6 +2439,8 @@ ResourceOwnerDesc
ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v22-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/x-diff; charset=us-asciiDownload
From 046d6e4b13d3a6b15df1245f3154969f7553594d Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v22 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index d82bcc2cfd..98a5aa3661 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -912,11 +912,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both archive_command and archive_library set"),
- errdetail("Only one of archive_command, archive_library may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3fb6803998..02bc4d66e7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot set both %s and %s", p1name, p2name),
+ errdetail("Only one of %s or %s may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index e4a594b5e8..018bb7e55b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.25.1
v22-0002-refactor-code-for-restoring-via-shell.patchtext/x-diff; charset=us-asciiDownload
From 88e5e696792efdb62f7067400223c67357db6dba Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v22 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 6700aec039..590b5002c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index c3fd9c1eae..1eb6d57fe2 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -704,6 +705,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5257,12 +5259,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7753,12 +7761,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9388,6 +9402,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index caa1f03d93..5756f52124 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 29c5bec084..07213b1897 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4209,7 +4209,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..cc665ce259 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,8 +169,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -194,6 +193,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -274,8 +283,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index dde7ebde88..6ba1e48c02 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2b83c340fb..886a49de6f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.25.1
v22-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/x-diff; charset=us-asciiDownload
From e8a18f516343f3a3eef66ca66ca6367cf4e24d75 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v22 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 38ec362d8f..be387bd7aa 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index ec9f90e283..d668ad89f6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -227,7 +227,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.25.1
v22-0004-restructure-archive-modules-docs-in-preparation-.patchtext/x-diff; charset=us-asciiDownload
From 45f660e43aaef84f6d59fa4c4f975a8f6751ba2e Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v22 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 242 +++++++++---------
1 file changed, 128 insertions(+), 114 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 10ec96eae9..6e368290b9 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,112 +72,119 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned or an error is thrown, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
-
- <note>
- <para>
- The <function>archive_file_cb</function> callback is called in a
- short-lived memory context that will be reset between invocations. If you
- need longer-lived storage, create a memory context in the module's
- <function>startup_cb</function> callback.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned or an error is
+ thrown, the server will keep the original WAL file and retry archiving
+ later. <replaceable>file</replaceable> will contain just the file name of
+ the WAL file to archive, while <replaceable>path</replaceable> contains the
+ full path of the WAL file (including the file name).
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <note>
+ <para>
+ The <function>archive_file_cb</function> callback is called in a
+ short-lived memory context that will be reset between invocations. If you
+ need longer-lived storage, create a memory context in the module's
+ <function>startup_cb</function> callback.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.25.1
v22-0005-introduce-restore_library.patchtext/x-diff; charset=us-asciiDownload
From 4a0e021e7b5d5ce1536fc96ba91dd125bb22e12a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 22:06:04 -0800
Subject: [PATCH v22 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 028cf51c25..e84bad27d2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -46,6 +52,16 @@ static bool basic_archive_configured(ArchiveModuleState *state);
static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = NULL,
@@ -54,6 +70,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = NULL
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -63,7 +94,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -85,6 +116,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* check_archive_directory
*
@@ -307,3 +349,112 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 6e368290b9..bcfa88dee0 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -187,4 +189,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 91da3c26ba..4469b45f60 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1265,9 +1265,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1286,14 +1303,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1317,7 +1340,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index e93208b2e6..6742b56a4f 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3909,7 +3909,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3937,7 +3938,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3972,7 +3974,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4017,7 +4054,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4046,7 +4086,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 1eb6d57fe2..b7a7f58009 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5258,18 +5258,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7760,18 +7761,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 5756f52124..3a05581495 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 07213b1897..1bf0d0ea11 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1117,18 +1118,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither primary_conninfo nor restore_command"),
+ (errmsg("specified neither primary_conninfo nor restore_command nor a configured restore_library"),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify restore_command when standby mode is not enabled")));
+ errmsg("must specify restore_command or a configured restore_library when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 8ef600ae72..c31d0f3bd2 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -46,6 +46,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -230,6 +231,25 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -562,6 +582,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -577,6 +601,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cc665ce259..f8d3d6b3d2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
#include "miscadmin.h"
#include "postmaster/auxprocess.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -119,13 +120,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -147,6 +152,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -261,6 +290,19 @@ StartupProcessMain(char *startup_data, size_t startup_data_len)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index ea2b0577bc..89d0756fa2 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3967,6 +3968,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 83d5df8e46..a9a2475c4b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -285,6 +285,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 886a49de6f..6eae455775 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2450,6 +2450,8 @@ ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
ResourceReleasePriority
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.25.1
rebased
--
Nathan Bossart
Amazon Web Services: https://aws.amazon.com
Attachments:
v23-0001-introduce-routine-for-checking-mutually-exclusiv.patchtext/plain; charset=us-asciiDownload
From c23ddbe1dac8b9a79db31ad67df423848e475905 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:28:53 -0800
Subject: [PATCH v23 1/5] introduce routine for checking mutually exclusive
string GUCs
---
src/backend/postmaster/pgarch.c | 8 +++-----
src/backend/utils/misc/guc.c | 22 ++++++++++++++++++++++
src/include/utils/guc.h | 3 +++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 02f91431f5..5f1a6f190d 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -912,11 +912,9 @@ LoadArchiveLibrary(void)
{
ArchiveModuleInit archive_init;
- if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("both \"archive_command\" and \"archive_library\" set"),
- errdetail("Only one of \"archive_command\", \"archive_library\" may be set.")));
+ (void) CheckMutuallyExclusiveStringGUCs(XLogArchiveLibrary, "archive_library",
+ XLogArchiveCommand, "archive_command",
+ ERROR);
/*
* If shell archiving is enabled, use our special initialization function.
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 547cecde24..05dc5303bc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -2659,6 +2659,28 @@ ReportGUCOption(struct config_generic *record)
pfree(val);
}
+/*
+ * If both parameters are set, emits a log message at 'elevel' and returns
+ * false. Otherwise, returns true.
+ */
+bool
+CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel)
+{
+ if (p1val[0] != '\0' && p2val[0] != '\0')
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("both \"%s\" and \"%s\" set", p1name, p2name),
+ errdetail("Only one of \"%s\", \"%s\" may be set.",
+ p1name, p2name)));
+ return false;
+ }
+
+ return true;
+}
+
/*
* Convert a value from one of the human-friendly units ("kB", "min" etc.)
* to the given base unit. 'value' and 'unit' are the input value and unit
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index e4a594b5e8..018bb7e55b 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -376,6 +376,9 @@ extern void RestrictSearchPath(void);
extern void AtEOXact_GUC(bool isCommit, int nestLevel);
extern void BeginReportingGUCOptions(void);
extern void ReportChangedGUCOptions(void);
+extern bool CheckMutuallyExclusiveStringGUCs(const char *p1val, const char *p1name,
+ const char *p2val, const char *p2name,
+ int elevel);
extern void ParseLongOption(const char *string, char **name, char **value);
extern const char *get_config_unit_name(int flags);
extern bool parse_int(const char *value, int *result, int flags,
--
2.39.3 (Apple Git-146)
v23-0002-refactor-code-for-restoring-via-shell.patchtext/plain; charset=us-asciiDownload
From b35cd2dd0b360a50af7ab9175b2887646ba57f37 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 10:36:00 -0800
Subject: [PATCH v23 2/5] refactor code for restoring via shell
---
src/backend/Makefile | 2 +-
src/backend/access/transam/timeline.c | 12 +-
src/backend/access/transam/xlog.c | 50 ++++-
src/backend/access/transam/xlogarchive.c | 167 ++++-----------
src/backend/access/transam/xlogrecovery.c | 3 +-
src/backend/meson.build | 1 +
src/backend/postmaster/startup.c | 16 +-
src/backend/restore/Makefile | 18 ++
src/backend/restore/meson.build | 5 +
src/backend/restore/shell_restore.c | 245 ++++++++++++++++++++++
src/include/Makefile | 2 +-
src/include/access/xlogarchive.h | 9 +-
src/include/meson.build | 1 +
src/include/postmaster/startup.h | 1 +
src/include/restore/shell_restore.h | 26 +++
src/tools/pgindent/typedefs.list | 1 +
16 files changed, 407 insertions(+), 152 deletions(-)
create mode 100644 src/backend/restore/Makefile
create mode 100644 src/backend/restore/meson.build
create mode 100644 src/backend/restore/shell_restore.c
create mode 100644 src/include/restore/shell_restore.h
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 6700aec039..590b5002c0 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global
SUBDIRS = access archive backup bootstrap catalog parser commands executor \
foreign lib libpq \
main nodes optimizer partitioning port postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop tsearch utils $(top_builddir)/src/timezone \
jit
diff --git a/src/backend/access/transam/timeline.c b/src/backend/access/transam/timeline.c
index 146751ae37..3269547de7 100644
--- a/src/backend/access/transam/timeline.c
+++ b/src/backend/access/transam/timeline.c
@@ -59,7 +59,8 @@ restoreTimeLineHistoryFiles(TimeLineID begin, TimeLineID end)
continue;
TLHistoryFileName(histfname, tli);
- if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false))
+ if (RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY))
KeepFileRestoredFromArchive(path, histfname);
}
}
@@ -97,7 +98,8 @@ readTimeLineHistory(TimeLineID targetTLI)
{
TLHistoryFileName(histfname, targetTLI);
fromArchive =
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, targetTLI);
@@ -232,7 +234,8 @@ existsTimeLineHistory(TimeLineID probeTLI)
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, probeTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS);
}
else
TLHistoryFilePath(path, probeTLI);
@@ -334,7 +337,8 @@ writeTimeLineHistory(TimeLineID newTLI, TimeLineID parentTLI,
if (ArchiveRecoveryRequested)
{
TLHistoryFileName(histfname, parentTLI);
- RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false);
+ RestoreArchivedFile(path, histfname, "RECOVERYHISTORY", 0, false,
+ ARCHIVE_TYPE_TIMELINE_HISTORY);
}
else
TLHistoryFilePath(path, parentTLI);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 330e058c5f..85b2fd9c1e 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,6 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -704,6 +705,7 @@ static char *GetXLogBuffer(XLogRecPtr ptr, TimeLineID tli);
static XLogRecPtr XLogBytePosToRecPtr(uint64 bytepos);
static XLogRecPtr XLogBytePosToEndRecPtr(uint64 bytepos);
static uint64 XLogRecPtrToBytePos(XLogRecPtr ptr);
+static void GetOldestRestartPointFileName(char *fname);
static void WALInsertLockAcquire(void);
static void WALInsertLockAcquireExclusive(void);
@@ -5257,12 +5259,18 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
{
/*
* Execute the recovery_end_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (recoveryEndCommand && strcmp(recoveryEndCommand, "") != 0)
- ExecuteRecoveryCommand(recoveryEndCommand,
- "recovery_end_command",
- true,
- WAIT_EVENT_RECOVERY_END_COMMAND);
+ if (shell_recovery_end_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_recovery_end(lastRestartPointFname);
+ }
/*
* We switched to a new timeline. Clean up segments on the old timeline.
@@ -7753,12 +7761,18 @@ CreateRestartPoint(int flags)
/*
* Finally, execute archive_cleanup_command, if any.
+ *
+ * The command is provided the archive file cutoff point for use during
+ * log shipping replication. All files earlier than this point can be
+ * deleted from the archive, though there is no requirement to do so.
*/
- if (archiveCleanupCommand && strcmp(archiveCleanupCommand, "") != 0)
- ExecuteRecoveryCommand(archiveCleanupCommand,
- "archive_cleanup_command",
- false,
- WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND);
+ if (shell_archive_cleanup_configured())
+ {
+ char lastRestartPointFname[MAXFNAMELEN];
+
+ GetOldestRestartPointFileName(lastRestartPointFname);
+ shell_archive_cleanup(lastRestartPointFname);
+ }
return true;
}
@@ -9388,6 +9402,22 @@ GetOldestRestartPoint(XLogRecPtr *oldrecptr, TimeLineID *oldtli)
LWLockRelease(ControlFileLock);
}
+/*
+ * Returns the WAL file name for the last checkpoint or restartpoint. This is
+ * the oldest WAL file that we still need if we have to restart recovery.
+ */
+static void
+GetOldestRestartPointFileName(char *fname)
+{
+ XLogRecPtr restartRedoPtr;
+ TimeLineID restartTli;
+ XLogSegNo restartSegNo;
+
+ GetOldestRestartPoint(&restartRedoPtr, &restartTli);
+ XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
+ XLogFileName(fname, restartTli, restartSegNo, wal_segment_size);
+}
+
/* Thin wrapper around ShutdownWalRcv(). */
void
XLogShutdownWalRcv(void)
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 81999b4820..56413c6a61 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,12 +23,12 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
-#include "common/percentrepl.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -53,12 +53,11 @@
bool
RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled)
+ bool cleanupEnabled, ArchiveType archive_type)
{
char xlogpath[MAXPGPATH];
- char *xlogRestoreCmd;
char lastRestartPointFname[MAXPGPATH];
- int rc;
+ bool ret = false;
struct stat stat_buf;
XLogSegNo restartSegNo;
XLogRecPtr restartRedoPtr;
@@ -72,8 +71,21 @@ RestoreArchivedFile(char *path, const char *xlogfname,
goto not_available;
/* In standby mode, restore_command might not be supplied */
- if (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0)
- goto not_available;
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ if (!shell_restore_file_configured())
+ goto not_available;
+ break;
+ }
/*
* When doing archive recovery, we always prefer an archived log file even
@@ -149,39 +161,33 @@ RestoreArchivedFile(char *path, const char *xlogfname,
else
XLogFileName(lastRestartPointFname, 0, 0, wal_segment_size);
- /* Build the restore command to execute */
- xlogRestoreCmd = BuildRestoreCommand(recoveryRestoreCommand,
- xlogpath, xlogfname,
- lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing restore command \"%s\"",
- xlogRestoreCmd)));
-
- fflush(NULL);
- pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
-
/*
- * PreRestoreCommand() informs the SIGTERM handler for the startup process
- * that it should proc_exit() right away. This is done for the duration
- * of the system() call because there isn't a good way to break out while
- * it is executing. Since we might call proc_exit() in a signal handler,
- * it is best to put any additional logic before or after the
- * PreRestoreCommand()/PostRestoreCommand() section.
+ * To ensure we are responsive to server shutdown, check for shutdown
+ * requests before and after restoring a file. If there is one, we exit
+ * right away.
*/
- PreRestoreCommand();
+ HandleStartupProcShutdownRequests();
/*
* Copy xlog from archival storage to XLOGDIR
*/
- rc = system(xlogRestoreCmd);
-
- PostRestoreCommand();
+ switch (archive_type)
+ {
+ case ARCHIVE_TYPE_WAL_SEGMENT:
+ ret = shell_restore_wal_segment(xlogfname, xlogpath,
+ lastRestartPointFname);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
+ ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ break;
+ }
- pgstat_report_wait_end();
- pfree(xlogRestoreCmd);
+ HandleStartupProcShutdownRequests();
- if (rc == 0)
+ if (ret)
{
/*
* command apparently succeeded, but let's make sure the file is
@@ -237,37 +243,6 @@ RestoreArchivedFile(char *path, const char *xlogfname,
}
}
- /*
- * Remember, we rollforward UNTIL the restore fails so failure here is
- * just part of the process... that makes it difficult to determine
- * whether the restore failed because there isn't an archive to restore,
- * or because the administrator has specified the restore program
- * incorrectly. We have to assume the former.
- *
- * However, if the failure was due to any sort of signal, it's best to
- * punt and abort recovery. (If we "return false" here, upper levels will
- * assume that recovery is complete and start up the database!) It's
- * essential to abort on child SIGINT and SIGQUIT, because per spec
- * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
- * those it's a good bet we should have gotten it too.
- *
- * On SIGTERM, assume we have received a fast shutdown request, and exit
- * cleanly. It's pure chance whether we receive the SIGTERM first, or the
- * child process. If we receive it first, the signal handler will call
- * proc_exit, otherwise we do it here. If we or the child process received
- * SIGTERM for any other reason than a fast shutdown request, postmaster
- * will perform an immediate shutdown when it sees us exiting
- * unexpectedly.
- *
- * We treat hard shell errors such as "command not found" as fatal, too.
- */
- if (wait_result_is_signal(rc, SIGTERM))
- proc_exit(1);
-
- ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
- (errmsg("could not restore file \"%s\" from archive: %s",
- xlogfname, wait_result_to_str(rc))));
-
not_available:
/*
@@ -281,74 +256,6 @@ not_available:
return false;
}
-/*
- * Attempt to execute an external shell command during recovery.
- *
- * 'command' is the shell command to be executed, 'commandName' is a
- * human-readable name describing the command emitted in the logs. If
- * 'failOnSignal' is true and the command is killed by a signal, a FATAL
- * error is thrown. Otherwise a WARNING is emitted.
- *
- * This is currently used for recovery_end_command and archive_cleanup_command.
- */
-void
-ExecuteRecoveryCommand(const char *command, const char *commandName,
- bool failOnSignal, uint32 wait_event_info)
-{
- char *xlogRecoveryCmd;
- char lastRestartPointFname[MAXPGPATH];
- int rc;
- XLogSegNo restartSegNo;
- XLogRecPtr restartRedoPtr;
- TimeLineID restartTli;
-
- Assert(command && commandName);
-
- /*
- * Calculate the archive file cutoff point for use during log shipping
- * replication. All files earlier than this point can be deleted from the
- * archive, though there is no requirement to do so.
- */
- GetOldestRestartPoint(&restartRedoPtr, &restartTli);
- XLByteToSeg(restartRedoPtr, restartSegNo, wal_segment_size);
- XLogFileName(lastRestartPointFname, restartTli, restartSegNo,
- wal_segment_size);
-
- /*
- * construct the command to be executed
- */
- xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r", lastRestartPointFname);
-
- ereport(DEBUG3,
- (errmsg_internal("executing %s \"%s\"", commandName, command)));
-
- /*
- * execute the constructed command
- */
- fflush(NULL);
- pgstat_report_wait_start(wait_event_info);
- rc = system(xlogRecoveryCmd);
- pgstat_report_wait_end();
-
- pfree(xlogRecoveryCmd);
-
- if (rc != 0)
- {
- /*
- * If the failure was due to any sort of signal, it's best to punt and
- * abort recovery. See comments in RestoreArchivedFile().
- */
- ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
- /*------
- translator: First %s represents a postgresql.conf parameter name like
- "recovery_end_command", the 2nd is the value of that parameter, the
- third an already translated error message. */
- (errmsg("%s \"%s\": %s", commandName,
- command, wait_result_to_str(rc))));
- }
-}
-
-
/*
* A file was restored from the archive under a temporary filename (path),
* and now we want to keep it. Rename it under the permanent filename in
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index b45b833172..4af31d5739 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -4209,7 +4209,8 @@ XLogFileRead(XLogSegNo segno, int emode, TimeLineID tli,
if (!RestoreArchivedFile(path, xlogfname,
"RECOVERYXLOG",
wal_segment_size,
- InRedo))
+ InRedo,
+ ARCHIVE_TYPE_WAL_SEGMENT))
return -1;
break;
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 436c04af08..42ff1b303d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -27,6 +27,7 @@ subdir('port')
subdir('postmaster')
subdir('regex')
subdir('replication')
+subdir('restore')
subdir('rewrite')
subdir('statistics')
subdir('storage')
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..cc665ce259 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -169,8 +169,7 @@ HandleStartupProcInterrupts(void)
/*
* Check if we were requested to exit without finishing recovery.
*/
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
/*
* Emergency bailout if postmaster has died. This is to avoid the
@@ -194,6 +193,16 @@ HandleStartupProcInterrupts(void)
ProcessLogMemoryContextInterrupt();
}
+/*
+ * If there is a pending shutdown request, exit.
+ */
+void
+HandleStartupProcShutdownRequests(void)
+{
+ if (shutdown_requested)
+ proc_exit(1);
+}
+
/* --------------------------------
* signal handler routines
@@ -274,8 +283,7 @@ PreRestoreCommand(void)
* shutdown request received just before this.
*/
in_restore_command = true;
- if (shutdown_requested)
- proc_exit(1);
+ HandleStartupProcShutdownRequests();
}
void
diff --git a/src/backend/restore/Makefile b/src/backend/restore/Makefile
new file mode 100644
index 0000000000..983d8e980f
--- /dev/null
+++ b/src/backend/restore/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+# Makefile for src/backend/restore
+#
+# IDENTIFICATION
+# src/backend/restore/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/restore
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+ shell_restore.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/restore/meson.build b/src/backend/restore/meson.build
new file mode 100644
index 0000000000..e4275b3d9c
--- /dev/null
+++ b/src/backend/restore/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+backend_sources += files(
+ 'shell_restore.c'
+)
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
new file mode 100644
index 0000000000..42291625c1
--- /dev/null
+++ b/src/backend/restore/shell_restore.c
@@ -0,0 +1,245 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.c
+ *
+ * These restore functions use a user-specified shell command (e.g., the
+ * restore_command GUC).
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/restore/shell_restore.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <signal.h>
+
+#include "access/xlog.h"
+#include "access/xlogrecovery.h"
+#include "access/xlog_internal.h"
+#include "common/archive.h"
+#include "common/percentrepl.h"
+#include "postmaster/startup.h"
+#include "restore/shell_restore.h"
+#include "storage/ipc.h"
+#include "utils/wait_event.h"
+
+static bool shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static void ExecuteRecoveryCommand(const char *command,
+ const char *commandName,
+ bool failOnSignal,
+ uint32 wait_event_info,
+ const char *lastRestartPointFileName);
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a WAL segment.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return shell_restore_file(file, path, lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a timeline
+ * history file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+bool
+shell_restore_timeline_history(const char *file, const char *path)
+{
+ char lastRestartPointFname[MAXPGPATH];
+
+ XLogFileName(lastRestartPointFname, 0, 0L, wal_segment_size);
+ return shell_restore_file(file, path, lastRestartPointFname);
+}
+
+/*
+ * Check whether restore_command is supplied.
+ */
+bool
+shell_restore_file_configured(void)
+{
+ return recoveryRestoreCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based restore command to retrieve a file.
+ *
+ * Returns true if the command has succeeded, false otherwise.
+ */
+static bool
+shell_restore_file(const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ char *cmd;
+ int rc;
+
+ /* Build the restore command to execute */
+ cmd = BuildRestoreCommand(recoveryRestoreCommand, path, file,
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing restore command \"%s\"", cmd)));
+
+ fflush(NULL);
+ pgstat_report_wait_start(WAIT_EVENT_RESTORE_COMMAND);
+
+ /*
+ * PreRestoreCommand() informs the SIGTERM handler for the startup process
+ * that it should proc_exit() right away. This is done for the duration
+ * of the system() call because there isn't a good way to break out while
+ * it is executing. Since we might call proc_exit() in a signal handler,
+ * it is best to put any additional logic before or after the
+ * PreRestoreCommand()/PostRestoreCommand() section.
+ */
+ PreRestoreCommand();
+
+ /*
+ * Copy xlog from archival storage to XLOGDIR
+ */
+ rc = system(cmd);
+
+ PostRestoreCommand();
+
+ pgstat_report_wait_end();
+ pfree(cmd);
+
+ /*
+ * Remember, we rollforward UNTIL the restore fails so failure here is
+ * just part of the process... that makes it difficult to determine
+ * whether the restore failed because there isn't an archive to restore,
+ * or because the administrator has specified the restore program
+ * incorrectly. We have to assume the former.
+ *
+ * However, if the failure was due to any sort of signal, it's best to
+ * punt and abort recovery. (If we "return false" here, upper levels will
+ * assume that recovery is complete and start up the database!) It's
+ * essential to abort on child SIGINT and SIGQUIT, because per spec
+ * system() ignores SIGINT and SIGQUIT while waiting; if we see one of
+ * those it's a good bet we should have gotten it too.
+ *
+ * On SIGTERM, assume we have received a fast shutdown request, and exit
+ * cleanly. It's pure chance whether we receive the SIGTERM first, or the
+ * child process. If we receive it first, the signal handler will call
+ * proc_exit, otherwise we do it here. If we or the child process received
+ * SIGTERM for any other reason than a fast shutdown request, postmaster
+ * will perform an immediate shutdown when it sees us exiting
+ * unexpectedly.
+ *
+ * We treat hard shell errors such as "command not found" as fatal, too.
+ */
+ if (rc != 0)
+ {
+ if (wait_result_is_signal(rc, SIGTERM))
+ proc_exit(1);
+
+ ereport(wait_result_is_any_signal(rc, true) ? FATAL : DEBUG2,
+ (errmsg("could not restore file \"%s\" from archive: %s",
+ file, wait_result_to_str(rc))));
+ }
+
+ return (rc == 0);
+}
+
+/*
+ * Check whether archive_cleanup_command is supplied.
+ */
+bool
+shell_archive_cleanup_configured(void)
+{
+ return archiveCleanupCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based archive cleanup command.
+ */
+void
+shell_archive_cleanup(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
+ false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Check whether recovery_end_command is supplied.
+ */
+bool
+shell_recovery_end_configured(void)
+{
+ return recoveryEndCommand[0] != '\0';
+}
+
+/*
+ * Attempt to execute a shell-based end-of-recovery command.
+ */
+void
+shell_recovery_end(const char *lastRestartPointFileName)
+{
+ ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
+ WAIT_EVENT_RECOVERY_END_COMMAND,
+ lastRestartPointFileName);
+}
+
+/*
+ * Attempt to execute an external shell command during recovery.
+ *
+ * 'command' is the shell command to be executed, 'commandName' is a
+ * human-readable name describing the command emitted in the logs. If
+ * 'failOnSignal' is true and the command is killed by a signal, a FATAL
+ * error is thrown. Otherwise a WARNING is emitted.
+ *
+ * This is currently used for recovery_end_command and archive_cleanup_command.
+ */
+static void
+ExecuteRecoveryCommand(const char *command, const char *commandName,
+ bool failOnSignal, uint32 wait_event_info,
+ const char *lastRestartPointFileName)
+{
+ char *xlogRecoveryCmd;
+ int rc;
+
+ Assert(command && commandName);
+
+ /*
+ * construct the command to be executed
+ */
+ xlogRecoveryCmd = replace_percent_placeholders(command, commandName, "r",
+ lastRestartPointFileName);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing %s \"%s\"", commandName, command)));
+
+ /*
+ * execute the constructed command
+ */
+ fflush(NULL);
+ pgstat_report_wait_start(wait_event_info);
+ rc = system(xlogRecoveryCmd);
+ pgstat_report_wait_end();
+
+ pfree(xlogRecoveryCmd);
+
+ if (rc != 0)
+ {
+ /*
+ * If the failure was due to any sort of signal, it's best to punt and
+ * abort recovery. See comments in shell_restore().
+ */
+ ereport((failOnSignal && wait_result_is_any_signal(rc, true)) ? FATAL : WARNING,
+ /*------
+ translator: First %s represents a postgresql.conf parameter name like
+ "recovery_end_command", the 2nd is the value of that parameter, the
+ third an already translated error message. */
+ (errmsg("%s \"%s\": %s", commandName,
+ command, wait_result_to_str(rc))));
+ }
+}
diff --git a/src/include/Makefile b/src/include/Makefile
index b8b576a4de..7e20f3e4c2 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -20,7 +20,7 @@ all: pg_config.h pg_config_ext.h pg_config_os.h
SUBDIRS = access archive bootstrap catalog commands common datatype \
executor fe_utils foreign jit \
lib libpq mb nodes optimizer parser partitioning postmaster \
- regex replication rewrite \
+ regex replication restore rewrite \
statistics storage tcop snowball snowball/libstemmer tsearch \
tsearch/dicts utils port port/atomics port/win32 port/win32_msvc \
port/win32_msvc/sys port/win32/arpa port/win32/netinet \
diff --git a/src/include/access/xlogarchive.h b/src/include/access/xlogarchive.h
index 0701475fb4..67e0fdcdec 100644
--- a/src/include/access/xlogarchive.h
+++ b/src/include/access/xlogarchive.h
@@ -17,9 +17,16 @@
#include "access/xlogdefs.h"
+typedef enum ArchiveType
+{
+ ARCHIVE_TYPE_WAL_SEGMENT,
+ ARCHIVE_TYPE_TIMELINE_HISTORY,
+ ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS
+} ArchiveType;
+
extern bool RestoreArchivedFile(char *path, const char *xlogfname,
const char *recovername, off_t expectedSize,
- bool cleanupEnabled);
+ bool cleanupEnabled, ArchiveType archive_type);
extern void ExecuteRecoveryCommand(const char *command, const char *commandName,
bool failOnSignal, uint32 wait_event_info);
extern void KeepFileRestoredFromArchive(const char *path, const char *xlogfname);
diff --git a/src/include/meson.build b/src/include/meson.build
index a28f115d86..3d30cf7972 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -150,6 +150,7 @@ header_subdirs = [
'postmaster',
'regex',
'replication',
+ 'restore',
'rewrite',
'statistics',
'storage',
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index dde7ebde88..6ba1e48c02 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -26,6 +26,7 @@
extern PGDLLIMPORT int log_startup_progress_interval;
extern void HandleStartupProcInterrupts(void);
+extern void HandleStartupProcShutdownRequests(void);
extern void StartupProcessMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
extern void PreRestoreCommand(void);
extern void PostRestoreCommand(void);
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
new file mode 100644
index 0000000000..28b5b7bc92
--- /dev/null
+++ b/src/include/restore/shell_restore.h
@@ -0,0 +1,26 @@
+/*-------------------------------------------------------------------------
+ *
+ * shell_restore.h
+ * Exports for restoring via shell.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/shell_restore.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _SHELL_RESTORE_H
+#define _SHELL_RESTORE_H
+
+extern bool shell_restore_file_configured(void);
+extern bool shell_restore_wal_segment(const char *file, const char *path,
+ const char *lastRestartPointFileName);
+extern bool shell_restore_timeline_history(const char *file, const char *path);
+
+extern bool shell_archive_cleanup_configured(void);
+extern void shell_archive_cleanup(const char *lastRestartPointFileName);
+
+extern bool shell_recovery_end_configured(void);
+extern void shell_recovery_end(const char *lastRestartPointFileName);
+
+#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 46a84c5714..b5e4608817 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -136,6 +136,7 @@ ArchiveOpts
ArchiveShutdownCB
ArchiveStartupCB
ArchiveStreamState
+ArchiveType
ArchiverOutput
ArchiverStage
ArrayAnalyzeExtraData
--
2.39.3 (Apple Git-146)
v23-0003-rename-archive-modules.sgml-to-archive-and-resto.patchtext/plain; charset=us-asciiDownload
From 07f0503c18f3b2348ede29244a040e1bee052e4a Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:45:17 -0800
Subject: [PATCH v23 3/5] rename archive-modules.sgml to
archive-and-restore-modules.sgml
---
.../{archive-modules.sgml => archive-and-restore-modules.sgml} | 0
doc/src/sgml/filelist.sgml | 2 +-
doc/src/sgml/postgres.sgml | 2 +-
3 files changed, 2 insertions(+), 2 deletions(-)
rename doc/src/sgml/{archive-modules.sgml => archive-and-restore-modules.sgml} (100%)
diff --git a/doc/src/sgml/archive-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
similarity index 100%
rename from doc/src/sgml/archive-modules.sgml
rename to doc/src/sgml/archive-and-restore-modules.sgml
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml
index 38ec362d8f..be387bd7aa 100644
--- a/doc/src/sgml/filelist.sgml
+++ b/doc/src/sgml/filelist.sgml
@@ -101,7 +101,7 @@
<!ENTITY custom-scan SYSTEM "custom-scan.sgml">
<!ENTITY logicaldecoding SYSTEM "logicaldecoding.sgml">
<!ENTITY replication-origins SYSTEM "replication-origins.sgml">
-<!ENTITY archive-modules SYSTEM "archive-modules.sgml">
+<!ENTITY archive-and-restore-modules SYSTEM "archive-and-restore-modules.sgml">
<!ENTITY protocol SYSTEM "protocol.sgml">
<!ENTITY sources SYSTEM "sources.sgml">
<!ENTITY storage SYSTEM "storage.sgml">
diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml
index ec9f90e283..d668ad89f6 100644
--- a/doc/src/sgml/postgres.sgml
+++ b/doc/src/sgml/postgres.sgml
@@ -227,7 +227,7 @@ break is not needed in a wider output rendering.
&bgworker;
&logicaldecoding;
&replication-origins;
- &archive-modules;
+ &archive-and-restore-modules;
</part>
--
2.39.3 (Apple Git-146)
v23-0004-restructure-archive-modules-docs-in-preparation-.patchtext/plain; charset=us-asciiDownload
From 55d201782396d051f580eea81a73d7cb412fc67b Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathandbossart@gmail.com>
Date: Wed, 15 Feb 2023 14:48:36 -0800
Subject: [PATCH v23 4/5] restructure archive modules docs in preparation for
restore modules
---
doc/src/sgml/archive-and-restore-modules.sgml | 242 +++++++++---------
1 file changed, 128 insertions(+), 114 deletions(-)
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 10ec96eae9..6e368290b9 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -1,9 +1,9 @@
-<!-- doc/src/sgml/archive-modules.sgml -->
+<!-- doc/src/sgml/archive-and-restore-modules.sgml -->
-<chapter id="archive-modules">
- <title>Archive Modules</title>
- <indexterm zone="archive-modules">
- <primary>Archive Modules</primary>
+<chapter id="archive-and-restore-modules">
+ <title>Archive and Restore Modules</title>
+ <indexterm zone="archive-and-restore-modules">
+ <primary>Archive and Restore Modules</primary>
</indexterm>
<para>
@@ -14,45 +14,52 @@
performant.
</para>
- <para>
- When a custom <xref linkend="guc-archive-library"/> is configured, PostgreSQL
- will submit completed WAL files to the module, and the server will avoid
- recycling or removing these WAL files until the module indicates that the files
- were successfully archived. It is ultimately up to the module to decide what
- to do with each WAL file, but many recommendations are listed at
- <xref linkend="backup-archiving-wal"/>.
- </para>
-
- <para>
- Archiving modules must at least consist of an initialization function (see
- <xref linkend="archive-module-init"/>) and the required callbacks (see
- <xref linkend="archive-module-callbacks"/>). However, archive modules are
- also permitted to do much more (e.g., declare GUCs and register background
- workers).
- </para>
-
<para>
The <filename>contrib/basic_archive</filename> module contains a working
example, which demonstrates some useful techniques.
</para>
- <sect1 id="archive-module-init">
- <title>Initialization Functions</title>
- <indexterm zone="archive-module-init">
- <primary>_PG_archive_module_init</primary>
+ <sect1 id="archive-modules">
+ <title>Archive Modules</title>
+ <indexterm zone="archive-modules">
+ <primary>Archive Modules</primary>
</indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-archive-library"/> is configured,
+ PostgreSQL will submit completed WAL files to the module, and the server
+ will avoid recycling or removing these WAL files until the module indicates
+ that the files were successfully archived. It is ultimately up to the
+ module to decide what to do with each WAL file, but many recommendations are
+ listed at <xref linkend="backup-archiving-wal"/>.
+ </para>
+
<para>
- An archive library is loaded by dynamically loading a shared library with the
- <xref linkend="guc-archive-library"/>'s name as the library base name. The
- normal library search path is used to locate the library. To provide the
- required archive module callbacks and to indicate that the library is
- actually an archive module, it needs to provide a function named
- <function>_PG_archive_module_init</function>. The result of the function
- must be a pointer to a struct of type
- <structname>ArchiveModuleCallbacks</structname>, which contains everything
- that the core code needs to know to make use of the archive module. The
- return value needs to be of server lifetime, which is typically achieved by
- defining it as a <literal>static const</literal> variable in global scope.
+ Archiving modules must at least consist of an initialization function (see
+ <xref linkend="archive-module-init"/>) and the required callbacks (see
+ <xref linkend="archive-module-callbacks"/>). However, archive modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="archive-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="archive-module-init">
+ <primary>_PG_archive_module_init</primary>
+ </indexterm>
+
+ <para>
+ An archive library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-archive-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required archive module callbacks and to indicate that the library is
+ actually an archive module, it needs to provide a function named
+ <function>_PG_archive_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>ArchiveModuleCallbacks</structname>, which contains everything
+ that the core code needs to know to make use of the archive module. The
+ return value needs to be of server lifetime, which is typically achieved by
+ defining it as a <literal>static const</literal> variable in global scope.
<programlisting>
typedef struct ArchiveModuleCallbacks
@@ -65,112 +72,119 @@ typedef struct ArchiveModuleCallbacks
typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void);
</programlisting>
- Only the <function>archive_file_cb</function> callback is required. The
- others are optional.
- </para>
- </sect1>
+ Only the <function>archive_file_cb</function> callback is required. The
+ others are optional.
+ </para>
+ </sect2>
- <sect1 id="archive-module-callbacks">
- <title>Archive Module Callbacks</title>
- <para>
- The archive callbacks define the actual archiving behavior of the module.
- The server will call them as required to process each individual WAL file.
- </para>
+ <sect2 id="archive-module-callbacks">
+ <title>Archive Module Callbacks</title>
- <sect2 id="archive-module-startup">
- <title>Startup Callback</title>
<para>
- The <function>startup_cb</function> callback is called shortly after the
- module is loaded. This callback can be used to perform any additional
- initialization required. If the archive module has any state, it can use
- <structfield>state->private_data</structfield> to store it.
+ The archive callbacks define the actual archiving behavior of the module.
+ The server will call them as required to process each individual WAL file.
+ </para>
+
+ <sect3 id="archive-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the archive module has any state, it can use
+ <structfield>state->private_data</structfield> to store it.
<programlisting>
typedef void (*ArchiveStartupCB) (ArchiveModuleState *state);
</programlisting>
- </para>
- </sect2>
+ </para>
+ </sect3>
- <sect2 id="archive-module-check">
- <title>Check Callback</title>
- <para>
- The <function>check_configured_cb</function> callback is called to determine
- whether the module is fully configured and ready to accept WAL files (e.g.,
- its configuration parameters are set to valid values). If no
- <function>check_configured_cb</function> is defined, the server always
- assumes the module is configured.
+ <sect3 id="archive-module-check">
+ <title>Check Callback</title>
+
+ <para>
+ The <function>check_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to accept WAL
+ files (e.g., its configuration parameters are set to valid values). If no
+ <function>check_configured_cb</function> is defined, the server always
+ assumes the module is configured.
<programlisting>
typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
</programlisting>
- If <literal>true</literal> is returned, the server will proceed with
- archiving the file by calling the <function>archive_file_cb</function>
- callback. If <literal>false</literal> is returned, archiving will not
- proceed, and the archiver will emit the following message to the server log:
+ If <literal>true</literal> is returned, the server will proceed with
+ archiving the file by calling the <function>archive_file_cb</function>
+ callback. If <literal>false</literal> is returned, archiving will not
+ proceed, and the archiver will emit the following message to the server
+ log:
<screen>
WARNING: archive_mode enabled, yet archiving is not configured
</screen>
- In the latter case, the server will periodically call this function, and
- archiving will proceed only when it returns <literal>true</literal>.
- </para>
-
- <note>
- <para>
- When returning <literal>false</literal>, it may be useful to append some
- additional information to the generic warning message. To do that, provide
- a message to the <function>arch_module_check_errdetail</function> macro
- before returning <literal>false</literal>. Like
- <function>errdetail()</function>, this macro accepts a format string
- followed by an optional list of arguments. The resulting string will be
- emitted as the <literal>DETAIL</literal> line of the warning message.
+ In the latter case, the server will periodically call this function, and
+ archiving will proceed only when it returns <literal>true</literal>.
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-archive">
- <title>Archive Callback</title>
- <para>
- The <function>archive_file_cb</function> callback is called to archive a
- single WAL file.
+ <note>
+ <para>
+ When returning <literal>false</literal>, it may be useful to append some
+ additional information to the generic warning message. To do that,
+ provide a message to the <function>arch_module_check_errdetail</function>
+ macro before returning <literal>false</literal>. Like
+ <function>errdetail()</function>, this macro accepts a format string
+ followed by an optional list of arguments. The resulting string will be
+ emitted as the <literal>DETAIL</literal> line of the warning message.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-archive">
+ <title>Archive Callback</title>
+
+ <para>
+ The <function>archive_file_cb</function> callback is called to archive a
+ single WAL file.
<programlisting>
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
</programlisting>
- If <literal>true</literal> is returned, the server proceeds as if the file
- was successfully archived, which may include recycling or removing the
- original WAL file. If <literal>false</literal> is returned or an error is thrown, the server will
- keep the original WAL file and retry archiving later.
- <replaceable>file</replaceable> will contain just the file name of the WAL
- file to archive, while <replaceable>path</replaceable> contains the full
- path of the WAL file (including the file name).
- </para>
-
- <note>
- <para>
- The <function>archive_file_cb</function> callback is called in a
- short-lived memory context that will be reset between invocations. If you
- need longer-lived storage, create a memory context in the module's
- <function>startup_cb</function> callback.
+ If <literal>true</literal> is returned, the server proceeds as if the file
+ was successfully archived, which may include recycling or removing the
+ original WAL file. If <literal>false</literal> is returned or an error is
+ thrown, the server will keep the original WAL file and retry archiving
+ later. <replaceable>file</replaceable> will contain just the file name of
+ the WAL file to archive, while <replaceable>path</replaceable> contains the
+ full path of the WAL file (including the file name).
</para>
- </note>
- </sect2>
- <sect2 id="archive-module-shutdown">
- <title>Shutdown Callback</title>
- <para>
- The <function>shutdown_cb</function> callback is called when the archiver
- process exits (e.g., after an error) or the value of
- <xref linkend="guc-archive-library"/> changes. If no
- <function>shutdown_cb</function> is defined, no special action is taken in
- these situations. If the archive module has any state, this callback should
- free it to avoid leaks.
+ <note>
+ <para>
+ The <function>archive_file_cb</function> callback is called in a
+ short-lived memory context that will be reset between invocations. If you
+ need longer-lived storage, create a memory context in the module's
+ <function>startup_cb</function> callback.
+ </para>
+ </note>
+ </sect3>
+
+ <sect3 id="archive-module-shutdown">
+ <title>Shutdown Callback</title>
+
+ <para>
+ The <function>shutdown_cb</function> callback is called when the archiver
+ process exits (e.g., after an error) or the value of
+ <xref linkend="guc-archive-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the archive module has any state, this callback
+ should free it to avoid leaks.
<programlisting>
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</programlisting>
- </para>
+ </para>
+ </sect3>
</sect2>
</sect1>
</chapter>
--
2.39.3 (Apple Git-146)
v23-0005-introduce-restore_library.patchtext/plain; charset=us-asciiDownload
From 6812b4add91d8a56984ab4c9bdfb0a19397ea9f3 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 20 May 2024 21:27:39 -0500
Subject: [PATCH v23 5/5] introduce restore_library
---
contrib/basic_archive/Makefile | 2 +
contrib/basic_archive/basic_archive.c | 153 +++++++-
contrib/basic_archive/meson.build | 5 +
contrib/basic_archive/t/001_restore.pl | 44 +++
doc/src/sgml/archive-and-restore-modules.sgml | 356 +++++++++++++++++-
doc/src/sgml/backup.sgml | 40 +-
doc/src/sgml/basic-archive.sgml | 31 +-
doc/src/sgml/config.sgml | 53 ++-
doc/src/sgml/high-availability.sgml | 18 +-
src/backend/access/transam/xlog.c | 20 +-
src/backend/access/transam/xlogarchive.c | 96 ++++-
src/backend/access/transam/xlogrecovery.c | 14 +-
src/backend/postmaster/checkpointer.c | 54 +++
src/backend/postmaster/startup.c | 44 ++-
src/backend/restore/shell_restore.c | 85 ++++-
src/backend/utils/misc/guc_tables.c | 11 +
src/backend/utils/misc/postgresql.conf.sample | 1 +
src/include/restore/restore_module.h | 143 +++++++
src/include/restore/shell_restore.h | 16 +-
src/tools/pgindent/typedefs.list | 2 +
20 files changed, 1104 insertions(+), 84 deletions(-)
create mode 100644 contrib/basic_archive/t/001_restore.pl
create mode 100644 src/include/restore/restore_module.h
diff --git a/contrib/basic_archive/Makefile b/contrib/basic_archive/Makefile
index 100ed81f12..e61f7d676f 100644
--- a/contrib/basic_archive/Makefile
+++ b/contrib/basic_archive/Makefile
@@ -10,6 +10,8 @@ REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/basic_archive/basic_archive.c
# typical installcheck users do not have (e.g. buildfarm clients).
NO_INSTALLCHECK = 1
+TAP_TESTS = 1
+
ifdef USE_PGXS
PG_CONFIG = pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 028cf51c25..e84bad27d2 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -17,6 +17,11 @@
* a file is successfully archived and then the system crashes before
* a durable record of the success has been made.
*
+ * This file also demonstrates a basic restore library implementation that
+ * is roughly equivalent to the following shell command:
+ *
+ * cp /path/to/archivedir/%f %p
+ *
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
@@ -33,6 +38,7 @@
#include "archive/archive_module.h"
#include "common/int.h"
#include "miscadmin.h"
+#include "restore/restore_module.h"
#include "storage/copydir.h"
#include "storage/fd.h"
#include "utils/guc.h"
@@ -46,6 +52,16 @@ static bool basic_archive_configured(ArchiveModuleState *state);
static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
+static bool basic_restore_configured(RestoreModuleState *state);
+static bool basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool basic_restore_file(const char *file, const char *path);
+static bool basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file,
+ const char *path);
static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = NULL,
@@ -54,6 +70,21 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.shutdown_cb = NULL
};
+static const RestoreModuleCallbacks basic_restore_callbacks = {
+ .startup_cb = NULL,
+ .restore_wal_segment_configured_cb = basic_restore_configured,
+ .restore_wal_segment_cb = basic_restore_wal_segment,
+ .restore_timeline_history_configured_cb = basic_restore_configured,
+ .restore_timeline_history_cb = basic_restore_timeline_history,
+ .timeline_history_exists_configured_cb = basic_restore_configured,
+ .timeline_history_exists_cb = basic_restore_timeline_history_exists,
+ .archive_cleanup_configured_cb = NULL,
+ .archive_cleanup_cb = NULL,
+ .recovery_end_configured_cb = NULL,
+ .recovery_end_cb = NULL,
+ .shutdown_cb = NULL
+};
+
/*
* _PG_init
*
@@ -63,7 +94,7 @@ void
_PG_init(void)
{
DefineCustomStringVariable("basic_archive.archive_directory",
- gettext_noop("Archive file destination directory."),
+ gettext_noop("Archive file source/destination directory."),
NULL,
&archive_directory,
"",
@@ -85,6 +116,17 @@ _PG_archive_module_init(void)
return &basic_archive_callbacks;
}
+/*
+ * _PG_restore_module_init
+ *
+ * Returns the module's restore callbacks.
+ */
+const RestoreModuleCallbacks *
+_PG_restore_module_init(void)
+{
+ return &basic_restore_callbacks;
+}
+
/*
* check_archive_directory
*
@@ -307,3 +349,112 @@ compare_files(const char *file1, const char *file2)
return ret;
}
+
+/*
+ * basic_restore_configured
+ *
+ * Checks that archive_directory is not blank.
+ */
+static bool
+basic_restore_configured(RestoreModuleState *state)
+{
+ return archive_directory != NULL && archive_directory[0] != '\0';
+}
+
+/*
+ * basic_restore_wal_segment
+ *
+ * Retrieves one archived WAL segment.
+ */
+static bool
+basic_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_timeline_history
+ *
+ * Retrieves one timeline history file.
+ */
+static bool
+basic_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ return basic_restore_file(file, path);
+}
+
+/*
+ * basic_restore_file
+ *
+ * Retrieves one file.
+ */
+static bool
+basic_restore_file(const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("restoring \"%s\" via basic_archive",
+ file)));
+
+ /*
+ * If the file doesn't exist, return false to indicate that there are no
+ * more files to restore.
+ */
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ copy_file(archive_path, path);
+
+ ereport(DEBUG1,
+ (errmsg("restored \"%s\" via basic_archive",
+ file)));
+ return true;
+}
+
+/*
+ * basic_restore_timeline_history_exists
+ *
+ * Check whether a timeline history file is present in the archive directory.
+ */
+static bool
+basic_restore_timeline_history_exists(RestoreModuleState *state,
+ const char *file, const char *path)
+{
+ char archive_path[MAXPGPATH];
+ struct stat st;
+
+ ereport(DEBUG1,
+ (errmsg("checking existence of \"%s\" via basic_archive",
+ file)));
+
+ snprintf(archive_path, MAXPGPATH, "%s/%s", archive_directory, file);
+ if (stat(archive_path, &st) != 0)
+ {
+ int elevel = (errno == ENOENT) ? DEBUG1 : ERROR;
+
+ ereport(elevel,
+ (errcode_for_file_access(),
+ errmsg("could not stat file \"%s\": %m",
+ archive_path)));
+ return false;
+ }
+
+ ereport(DEBUG1,
+ (errmsg("verified existence of \"%s\" via basic_archive",
+ file)));
+ return true;
+}
diff --git a/contrib/basic_archive/meson.build b/contrib/basic_archive/meson.build
index cc2f62bf36..8e36fa7ed6 100644
--- a/contrib/basic_archive/meson.build
+++ b/contrib/basic_archive/meson.build
@@ -31,4 +31,9 @@ tests += {
# typical installcheck users do not have (e.g. buildfarm clients).
'runningcheck': false,
},
+ 'tap': {
+ 'tests': [
+ 't/001_restore.pl',
+ ],
+ },
}
diff --git a/contrib/basic_archive/t/001_restore.pl b/contrib/basic_archive/t/001_restore.pl
new file mode 100644
index 0000000000..6c01f736cf
--- /dev/null
+++ b/contrib/basic_archive/t/001_restore.pl
@@ -0,0 +1,44 @@
+
+# Copyright (c) 2024, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+
+# start a node
+my $node = PostgreSQL::Test::Cluster->new('node');
+$node->init(has_archiving => 1, allows_streaming => 1);
+my $archive_dir = $node->archive_dir;
+$archive_dir =~ s!\\!/!g if $PostgreSQL::Test::Utils::windows_os;
+$node->append_conf('postgresql.conf', "archive_command = ''");
+$node->append_conf('postgresql.conf', "archive_library = 'basic_archive'");
+$node->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$node->start;
+
+# backup the node
+my $backup = 'backup';
+$node->backup($backup);
+
+# generate some new WAL files
+$node->safe_psql('postgres', "CREATE TABLE test (a INT);");
+$node->safe_psql('postgres', "SELECT pg_switch_wal();");
+$node->safe_psql('postgres', "INSERT INTO test VALUES (1);");
+
+# shut down the node (this should archive all WAL files)
+$node->stop;
+
+# restore from the backup
+my $restore = PostgreSQL::Test::Cluster->new('restore');
+$restore->init_from_backup($node, $backup, has_restoring => 1, standby => 0);
+$restore->append_conf('postgresql.conf', "restore_command = ''");
+$restore->append_conf('postgresql.conf', "restore_library = 'basic_archive'");
+$restore->append_conf('postgresql.conf', "basic_archive.archive_directory = '$archive_dir'");
+$restore->start;
+
+# ensure post-backup WAL is replayed
+my $result = $restore->poll_query_until("postgres", "SELECT count(*) = 1 FROM test;");
+is($result, "1", "check restore content");
+
+done_testing();
diff --git a/doc/src/sgml/archive-and-restore-modules.sgml b/doc/src/sgml/archive-and-restore-modules.sgml
index 6e368290b9..bcfa88dee0 100644
--- a/doc/src/sgml/archive-and-restore-modules.sgml
+++ b/doc/src/sgml/archive-and-restore-modules.sgml
@@ -8,15 +8,17 @@
<para>
PostgreSQL provides infrastructure to create custom modules for continuous
- archiving (see <xref linkend="continuous-archiving"/>). While archiving via
- a shell command (i.e., <xref linkend="guc-archive-command"/>) is much
- simpler, a custom archive module will often be considerably more robust and
- performant.
+ archiving and recovery (see <xref linkend="continuous-archiving"/>). While a
+ shell command (i.e., <xref linkend="guc-archive-command"/>,
+ <xref linkend="guc-restore-command"/>) is much simpler, a custom module will
+ often be considerably more robust and performant.
</para>
<para>
The <filename>contrib/basic_archive</filename> module contains a working
- example, which demonstrates some useful techniques.
+ example, which demonstrates some useful techniques. As demonstrated in
+ <filename>basic_archive</filename> a single module may serve as both an
+ archive module and a restore module.
</para>
<sect1 id="archive-modules">
@@ -187,4 +189,348 @@ typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
</sect3>
</sect2>
</sect1>
+
+ <sect1 id="restore-modules">
+ <title>Restore Modules</title>
+ <indexterm zone="restore-modules">
+ <primary>Restore Modules</primary>
+ </indexterm>
+
+ <para>
+ When a custom <xref linkend="guc-restore-library"/> is configured,
+ PostgreSQL will use the module for restore actions. It is
+ ultimately up to the module to decide how to accomplish each task,
+ but some recommendations are listed at
+ <xref linkend="backup-pitr-recovery"/>.
+ </para>
+
+ <para>
+ Restore modules must at least consist of an initialization function
+ (see <xref linkend="restore-module-init"/>) and the required callbacks (see
+ <xref linkend="restore-module-callbacks"/>). However, restore modules are
+ also permitted to do much more (e.g., declare GUCs and register background
+ workers).
+ </para>
+
+ <sect2 id="restore-module-init">
+ <title>Initialization Functions</title>
+ <indexterm zone="restore-module-init">
+ <primary>_PG_restore_module_init</primary>
+ </indexterm>
+
+ <para>
+ An restore library is loaded by dynamically loading a shared library with
+ the <xref linkend="guc-restore-library"/>'s name as the library base name.
+ The normal library search path is used to locate the library. To provide
+ the required restore module callbacks and to indicate that the library is
+ actually a restore module, it needs to provide a function named
+ <function>_PG_restore_module_init</function>. The result of the function
+ must be a pointer to a struct of type
+ <structname>RestoreModuleCallbacks</structname>, which contains everything
+ that the core code needs to know how to make use of the restore module.
+ The return value needs to be of server lifetime, which is typically
+ achieved by defining it as a <literal>static const</literal> variable in
+ global scope.
+
+<programlisting>
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+</programlisting>
+
+ The <function>restore_wal_segment_configured_cb</function>,
+ <function>restore_wal_segment_cb</function>,
+ <function>restore_timeline_history_configured_cb</function>,
+ <function>restore_timeline_history_cb</function>,
+ <function>timeline_history_exists_configured_cb</function>, and
+ <function>timeline_history_exists_cb</function> callbacks are required for
+ archive recovery but optional for streaming replication. The others are
+ always optional.
+ </para>
+ </sect2>
+
+ <sect2 id="restore-module-callbacks">
+ <title>Restore Module Callbacks</title>
+
+ <para>
+ The restore callbacks define the actual behavior of the module. The server
+ will call them as required to execute restore actions.
+ </para>
+
+ <sect3 id="restore-module-startup">
+ <title>Startup Callback</title>
+
+ <para>
+ The <function>startup_cb</function> callback is called shortly after the
+ module is loaded. This callback can be used to perform any additional
+ initialization required. If the restore module has state, it can use
+ <structfield>state->private_data</structfield> to store it.
+
+<programlisting>
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+</programlisting>
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment-configured">
+ <title>Restore WAL Segment Configured Callback</title>
+ <para>
+ The <function>restore_wal_segment_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to retrieve WAL files (e.g., its configuration parameters
+ are set to valid values). If no
+ <function>restore_wal_segment_configured_cb</function> is defined, the
+ server always assumes the module is not configured to retrieve WAL files.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve WAL files
+ by calling the <function>restore_wal_segment_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_wal_segment_cb</function> callback to retrieve WAL
+ files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-wal-segment">
+ <title>Restore WAL Segment Callback</title>
+ <para>
+ The <function>restore_wal_segment_cb</function> callback is called to
+ retrieve a single archived segment of the WAL file series for archive
+ recovery or streaming replication.
+
+<programlisting>
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state, const char *file, const char *path, const char *lastRestartPointFileName);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point. That is the earliest
+ file that must be kept to allow a restore to be restartable, so this
+ information can be used to truncate the archive to just the minimum
+ required to support restarting from the current restore.
+ <replaceable>lastRestartPointFileName</replaceable> is typically only used
+ by warm-standby configurations (see <xref linkend="warm-standby"/>). Note
+ that if multiple standby servers are restoring from the same archive
+ directory, you will need to ensure that you do not delete WAL files until
+ they are no longer needed by any of the servers.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history-configured">
+ <title>Restore Timeline History Configured Callback</title>
+ <para>
+ The <function>restore_timeline_history_configured_cb</function> callback
+ is called to determine whether the module is fully configured and ready to
+ accept requests to retrieve timeline history files (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>restore_timeline_history_configured_cb</function> is defined,
+ the server always assumes the module is not configured to retrieve
+ timeline history files.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will retrieve timeline
+ history files by calling the
+ <function>restore_timeline_history_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>restore_timeline_history_cb</function> callback to retrieve
+ timeline history files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-restore-timeline-history">
+ <title>Restore Timeline History Callback</title>
+ <para>
+ The <function>restore_timeline_history_cb</function> callback is called to
+ retrieve a single archived timeline history file for archive recovery or
+ streaming replication.
+
+<programlisting>
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file was
+ successfully retrieved. If the file is not available in the archives, the
+ callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the WAL file to retrieve, while <replaceable>path</replaceable>
+ contains the destination's relative path (including the file name).
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists-configured">
+ <title>Timeline History Exists Configured Callback</title>
+ <para>
+ The <function>timeline_history_exists_configured_cb</function> callback is
+ called to determine whether the module is fully configured and ready to
+ accept requests to determine whether a timeline history file exists in the
+ archives (e.g., its configuration parameters are set to valid values). If
+ no <function>timeline_history_exists_configured_cb</function> is defined,
+ the server always assumes the module is not configured to check whether
+ certain timeline history files exist.
+
+<programlisting>
+typedef bool (*TimelineHistoryConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will check whether
+ certain timeline history files are present in the archives by calling the
+ <function>timeline_history_exists_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>timeline_history_exists_cb</function> callback to check if
+ timeline history files exist in the archives.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-timeline-history-exists">
+ <title>Timeline History Exists Callback</title>
+ <para>
+ The <function>timeline_history_exists_cb</function> callback is called to
+ check whether a timeline history file is present in the archives.
+
+<programlisting>
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state, const char *file, const char *path);
+</programlisting>
+
+ This callback must return <literal>true</literal> only if the file is
+ present in the archives. If the file is not available in the archives,
+ the callback must return <literal>false</literal>.
+ <replaceable>file</replaceable> will contain just the file name
+ of the timeline history file.
+ </para>
+
+ <para>
+ Some restore modules might not have a dedicated way to determine whether a
+ timeline history file exists. For example,
+ <varname>restore_command</varname> only tells the server how to retrieve
+ files. In this case, the <function>timeline_history_exists_cb</function>
+ callback should copy the file to <replaceable>path</replaceable>, which
+ contains the destination's relative path (including the file name), and
+ the restore module should set
+ <structfield>state->timeline_history_exists_cb_copies</structfield> to
+ <literal>true</literal>. It is recommended to set this flag in the
+ module's <function>startup_cb</function> callback. This flag tells the
+ server that it should verify that the file was successfully retrieved
+ after invoking this callback.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup-configured">
+ <title>Archive Cleanup Configured Callback</title>
+ <para>
+ The <function>archive_cleanup_configured_cb</function> callback is called
+ to determine whether the module is fully configured and ready to accept
+ requests to remove old WAL files from the archives (e.g., its
+ configuration parameters are set to valid values). If no
+ <function>archive_cleanup_configured_cb</function> is defined, the server
+ always assumes the module is not configured to remove old WAL files.
+
+<programlisting>
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will remove old WAL
+ files by calling the <function>archive_cleanup_cb</function> callback. If
+ <literal>false</literal> is returned, the server will not use the
+ <function>archive_cleanup_cb</function> callback to remove old WAL files.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-archive-cleanup">
+ <title>Archive Cleanup Callback</title>
+ <para>
+ The <function>archive_cleanup_cb</function> callback is called at every
+ restart point by the checkpointer process and is intended to provide a
+ mechanism for cleaning up old archived WAL files that are no longer
+ needed by the standby server.
+
+<programlisting>
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the
+ name of the file that includes the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end-configured">
+ <title>Recovery End Configured Callback</title>
+ <para>
+ The <function>recovery_end_configured_cb</function> callback is called to
+ determine whether the module is fully configured and ready to execute its
+ <function>recovery_end_cb</function> callback once at the end of recovery
+ (e.g., its configuration parameters are set to valid values). If no
+ <function>recovery_end_configured_cb</function> is defined, the server
+ always assumes the module is not configured to execute its
+ <function>recovery_end_cb</function> callback.
+
+<programlisting>
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+</programlisting>
+
+ If <literal>true</literal> is returned, the server will execute the
+ <function>recovery_end_cb</function> callback once at the end of recovery.
+ If <literal>false</literal> is returned, the server will not use the
+ <function>recovery_end_cb</function> callback at the end of recovery.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-recovery-end">
+ <title>Recovery End Callback</title>
+ <para>
+ The <function>recovery_end_cb</function> callback is called once at the
+ end of recovery and is intended to provide a mechanism for cleanup
+ following the end of replication or recovery.
+
+<programlisting>
+typedef void (*RecoveryEndCB) (RestoreModuleState *state, const char *lastRestartPointFileName);
+</programlisting>
+
+ <replaceable>lastRestartPointFileName</replaceable> will contain the name
+ of the file containing the last valid restart point, like in
+ <link linkend="restore-module-restore-wal-segment"><function>restore_wal_segment_cb</function></link>.
+ </para>
+ </sect3>
+
+ <sect3 id="restore-module-shutdown">
+ <title>Shutdown Callback</title>
+ <para>
+ The <function>shutdown_cb</function> callback is called when a process
+ that has loaded the restore module exits (e.g., after an error) or the
+ value of <xref linkend="guc-restore-library"/> changes. If no
+ <function>shutdown_cb</function> is defined, no special action is taken in
+ these situations. If the restore module has state, this callback should
+ free it to avoid leaks.
+
+<programlisting>
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+ </programlisting>
+ </para>
+ </sect3>
+ </sect2>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/backup.sgml b/doc/src/sgml/backup.sgml
index 91da3c26ba..4469b45f60 100644
--- a/doc/src/sgml/backup.sgml
+++ b/doc/src/sgml/backup.sgml
@@ -1265,9 +1265,26 @@ SELECT * FROM pg_backup_stop(wait_for_archive => true);
<para>
The key part of all this is to set up a recovery configuration that
describes how you want to recover and how far the recovery should
- run. The one thing that you absolutely must specify is the <varname>restore_command</varname>,
+ run. The one thing that you absolutely must specify is either
+ <varname>restore_command</varname> or a <varname>restore_library</varname>,
which tells <productname>PostgreSQL</productname> how to retrieve archived
- WAL file segments. Like the <varname>archive_command</varname>, this is
+ WAL file segments.
+ </para>
+
+ <para>
+ Like the <varname>archive_library</varname> parameter,
+ <varname>restore_library</varname> is a shared library. Since such
+ libraries are written in <literal>C</literal>, creating your own may
+ require considerably more effort than writing a shell command. However,
+ restore modules can be more performance than restoring via shell, and they
+ will have access to many useful server resources. For more information
+ about creating a <varname>restore_library</varname>, see
+ <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ Like the <varname>archive_command</varname>,
+ <varname>restore_command</varname> is
a shell command string. It can contain <literal>%f</literal>, which is
replaced by the name of the desired WAL file, and <literal>%p</literal>,
which is replaced by the path name to copy the WAL file to.
@@ -1286,14 +1303,20 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
</para>
<para>
- It is important that the command return nonzero exit status on failure.
- The command <emphasis>will</emphasis> be called requesting files that are not
- present in the archive; it must return nonzero when so asked. This is not
- an error condition. An exception is that if the command was terminated by
+ It is important that the <varname>restore_command</varname> return nonzero
+ exit status on failure, or, if you are using a
+ <varname>restore_library</varname>, that the restore callbacks return
+ <literal>false</literal> on failure. The command or library
+ <emphasis>will</emphasis> be called requesting files that are not
+ present in the archive; it must fail when so asked. This is not
+ an error condition. An exception is that if the
+ <varname>restore_command</varname> was terminated by
a signal (other than <systemitem>SIGTERM</systemitem>, which is used as
part of a 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.
+ up. Likewise, if the restore callbacks provided by the
+ <varname>restore_library</varname> emit an <literal>ERROR</literal> or
+ <literal>FATAL</literal>, recovery will abort and the server won't start.
</para>
<para>
@@ -1317,7 +1340,8 @@ restore_command = 'cp /mnt/server/archivedir/%f %p'
close as possible given the available WAL segments). Therefore, a normal
recovery will end with a <quote>file not found</quote> message, the exact text
of the error message depending upon your choice of
- <varname>restore_command</varname>. You may also see an error message
+ <varname>restore_command</varname> or <varname>restore_library</varname>.
+ You may also see an error message
at the start of recovery for a file named something like
<filename>00000001.history</filename>. This is also normal and does not
indicate a problem in simple recovery situations; see
diff --git a/doc/src/sgml/basic-archive.sgml b/doc/src/sgml/basic-archive.sgml
index b4d43ced20..f3a5a502bd 100644
--- a/doc/src/sgml/basic-archive.sgml
+++ b/doc/src/sgml/basic-archive.sgml
@@ -8,17 +8,20 @@
</indexterm>
<para>
- <filename>basic_archive</filename> is an example of an archive module. This
- module copies completed WAL segment files to the specified directory. This
- may not be especially useful, but it can serve as a starting point for
- developing your own archive module. For more information about archive
- modules, see <xref linkend="archive-modules"/>.
+ <filename>basic_archive</filename> is an example of an archive and restore
+ module. This module copies completed WAL segment files to or from the
+ specified directory. This may not be especially useful, but it can serve as
+ a starting point for developing your own archive and restore modules. For
+ more information about archive and restore modules, see\
+ <xref linkend="archive-and-restore-modules"/>.
</para>
<para>
- In order to function, this module must be loaded via
+ For use as an archive module, this module must be loaded via
<xref linkend="guc-archive-library"/>, and <xref linkend="guc-archive-mode"/>
- must be enabled.
+ must be enabled. For use as a restore module, this module must be loaded
+ via <xref linkend="guc-restore-library"/>, and recovery must be enabled (see
+ <xref linkend="runtime-config-wal-archive-recovery"/>).
</para>
<sect2 id="basic-archive-configuration-parameters">
@@ -34,11 +37,12 @@
</term>
<listitem>
<para>
- The directory where the server should copy WAL segment files. This
- directory must already exist. The default is an empty string, which
- effectively halts WAL archiving, but if <xref linkend="guc-archive-mode"/>
- is enabled, the server will accumulate WAL segment files in the
- expectation that a value will soon be provided.
+ The directory where the server should copy WAL segment files to or from.
+ This directory must already exist. The default is an empty string,
+ which, when used for archiving, effectively halts WAL archival, but if
+ <xref linkend="guc-archive-mode"/> is enabled, the server will accumulate
+ WAL segment files in the expectation that a value will soon be provided.
+ When an empty string is used for recovery, restore will fail.
</para>
</listitem>
</varlistentry>
@@ -46,7 +50,7 @@
<para>
These parameters must be set in <filename>postgresql.conf</filename>.
- Typical usage might be:
+ Typical usage as an archive module might be:
</para>
<programlisting>
@@ -61,6 +65,7 @@ basic_archive.archive_directory = '/path/to/archive/directory'
<title>Notes</title>
<para>
+ When <filename>basic_archive</filename> is used as an archive module,
Server crashes may leave temporary files with the prefix
<filename>archtemp</filename> in the archive directory. It is recommended to
delete such files before restarting the server after a crash. It is safe to
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 698169afdb..6e791a5a36 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -3916,7 +3916,8 @@ include_dir 'conf.d'
recovery when the end of archived WAL is reached, but will keep trying to
continue recovery by connecting to the sending server as specified by the
<varname>primary_conninfo</varname> setting and/or by fetching new WAL
- segments using <varname>restore_command</varname>. For this mode, the
+ segments using <varname>restore_command</varname> or
+ <varname>restore_library</varname>. For this mode, the
parameters from this section and <xref
linkend="runtime-config-replication-standby"/> are of interest.
Parameters from <xref linkend="runtime-config-wal-recovery-target"/> will
@@ -3944,7 +3945,8 @@ include_dir 'conf.d'
<listitem>
<para>
The local shell command to execute to retrieve an archived segment of
- the WAL file series. This parameter is required for archive recovery,
+ the WAL file series. Either <varname>restore_command</varname> or
+ <xref linkend="guc-restore-library"/> is required for archive recovery,
but optional for streaming replication.
Any <literal>%f</literal> in the string is
replaced by the name of the file to retrieve from the archive,
@@ -3979,7 +3981,42 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>restore_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="guc-restore-library" xreflabel="restore_library">
+ <term><varname>restore_library</varname> (<type>string</type>)
+ <indexterm>
+ <primary><varname>restore_library</varname> configuration parameter</primary>
+ </indexterm>
+ </term>
+ <listitem>
+ <para>
+ The library to use for restore actions, including retrieving archived
+ files and executing tasks at restartpoints and at recovery end. Either
+ <xref linkend="guc-restore-command"/> or
+ <varname>restore_library</varname> is required for archive recovery,
+ but optional for streaming replication. If this parameter is set to an
+ empty string (the default), restoring via shell is enabled, and
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, and
+ <varname>recovery_end_command</varname> are used. If both
+ <varname>restore_library</varname> and any of
+ <varname>restore_command</varname>,
+ <varname>archive_cleanup_command</varname>, or
+ <varname>recovery_end_command</varname> are set, an error will be
+ raised. Otherwise, the specified shared library is used for restoring.
+ For more information, see <xref linkend="restore-modules"/>.
+ </para>
+
+ <para>
+ This parameter can only be set in the
+ <filename>postgresql.conf</filename> file or on the server command line.
</para>
</listitem>
</varlistentry>
@@ -4024,7 +4061,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>archive_cleanup_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
@@ -4053,7 +4093,10 @@ restore_command = 'copy "C:\\server\\archivedir\\%f" "%p"' # Windows
</para>
<para>
This parameter can only be set in the <filename>postgresql.conf</filename>
- file or on the server command line.
+ file or on the server command line. It is only used if
+ <varname>restore_library</varname> is set to an empty string. If both
+ <varname>recovery_end_command</varname> and
+ <varname>restore_library</varname> are set, an error will be raised.
</para>
</listitem>
</varlistentry>
diff --git a/doc/src/sgml/high-availability.sgml b/doc/src/sgml/high-availability.sgml
index b48209fc2f..a86a9fe93a 100644
--- a/doc/src/sgml/high-availability.sgml
+++ b/doc/src/sgml/high-availability.sgml
@@ -627,7 +627,8 @@ protocol to make nodes agree on a serializable transactional order.
<para>
In standby mode, the server continuously applies WAL received from the
primary server. The standby server can read WAL from a WAL archive
- (see <xref linkend="guc-restore-command"/>) or directly from the primary
+ (see <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/>) or directly from the primary
over a TCP connection (streaming replication). The standby server will
also attempt to restore any WAL found in the standby cluster's
<filename>pg_wal</filename> directory. That typically happens after a server
@@ -638,8 +639,10 @@ protocol to make nodes agree on a serializable transactional order.
<para>
At startup, the standby begins by restoring all WAL available in the
- archive location, calling <varname>restore_command</varname>. Once it
+ archive location, either by calling <varname>restore_command</varname> or
+ by executing the <varname>restore_library</varname>'s callbacks. Once it
reaches the end of WAL available there and <varname>restore_command</varname>
+ or <varname>restore_library</varname>
fails, it tries to restore any WAL available in the <filename>pg_wal</filename> directory.
If that fails, and streaming replication has been configured, the
standby tries to connect to the primary server and start streaming WAL
@@ -698,7 +701,8 @@ protocol to make nodes agree on a serializable transactional order.
server (see <xref linkend="backup-pitr-recovery"/>). Create a file
<link linkend="file-standby-signal"><filename>standby.signal</filename></link><indexterm><primary>standby.signal</primary></indexterm>
in the standby's cluster data
- directory. Set <xref linkend="guc-restore-command"/> to a simple command to copy files from
+ directory. Set <xref linkend="guc-restore-command"/> or
+ <xref linkend="guc-restore-library"/> to copy files from
the WAL archive. If you plan to have multiple standby servers for high
availability purposes, make sure that <varname>recovery_target_timeline</varname> is set to
<literal>latest</literal> (the default), to make the standby server follow the timeline change
@@ -707,7 +711,8 @@ protocol to make nodes agree on a serializable transactional order.
<note>
<para>
- <xref linkend="guc-restore-command"/> should return immediately
+ <xref linkend="guc-restore-command"/> and
+ <xref linkend="guc-restore-library"/> should return immediately
if the file does not exist; the server will retry the command again if
necessary.
</para>
@@ -731,8 +736,9 @@ protocol to make nodes agree on a serializable transactional order.
<para>
If you're using a WAL archive, its size can be minimized using the <xref
- linkend="guc-archive-cleanup-command"/> parameter to remove files that are no
- longer required by the standby server.
+ linkend="guc-archive-cleanup-command"/> parameter or the
+ <xref linkend="guc-restore-library"/>'s archive cleanup callbacks to remove
+ files that are no longer required by the standby server.
The <application>pg_archivecleanup</application> utility is designed specifically to
be used with <varname>archive_cleanup_command</varname> in typical single-standby
configurations, see <xref linkend="pgarchivecleanup"/>.
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 85b2fd9c1e..cb7c53de50 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -83,7 +83,7 @@
#include "replication/snapbuild.h"
#include "replication/walreceiver.h"
#include "replication/walsender.h"
-#include "restore/shell_restore.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/fd.h"
#include "storage/ipc.h"
@@ -5258,18 +5258,19 @@ CleanupAfterArchiveRecovery(TimeLineID EndOfLogTLI, XLogRecPtr EndOfLog,
TimeLineID newTLI)
{
/*
- * Execute the recovery_end_command, if any.
+ * Execute the recovery-end callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_recovery_end_configured())
+ if (recovery_end_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_recovery_end(lastRestartPointFname);
+ RestoreCallbacks->recovery_end_cb(restore_module_state,
+ lastRestartPointFname);
}
/*
@@ -7760,18 +7761,19 @@ CreateRestartPoint(int flags)
timestamptz_to_str(xtime)) : 0));
/*
- * Finally, execute archive_cleanup_command, if any.
+ * Finally, execute archive-cleanup callback, if any.
*
- * The command is provided the archive file cutoff point for use during
+ * The callback is provided the archive file cutoff point for use during
* log shipping replication. All files earlier than this point can be
* deleted from the archive, though there is no requirement to do so.
*/
- if (shell_archive_cleanup_configured())
+ if (archive_cleanup_configured())
{
char lastRestartPointFname[MAXFNAMELEN];
GetOldestRestartPointFileName(lastRestartPointFname);
- shell_archive_cleanup(lastRestartPointFname);
+ RestoreCallbacks->archive_cleanup_cb(restore_module_state,
+ lastRestartPointFname);
}
return true;
diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c
index 56413c6a61..f6fe65e8ed 100644
--- a/src/backend/access/transam/xlogarchive.c
+++ b/src/backend/access/transam/xlogarchive.c
@@ -23,14 +23,21 @@
#include "access/xlog_internal.h"
#include "access/xlogarchive.h"
#include "common/archive.h"
+#include "fmgr.h"
#include "miscadmin.h"
#include "pgstat.h"
#include "postmaster/pgarch.h"
#include "postmaster/startup.h"
#include "replication/walsender.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/fd.h"
#include "storage/ipc.h"
+#include "utils/memutils.h"
+
+char *restoreLibrary = "";
+const RestoreModuleCallbacks *RestoreCallbacks = NULL;
+RestoreModuleState *restore_module_state;
/*
* Attempt to retrieve the specified file from off-line archival storage.
@@ -74,15 +81,15 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- if (!shell_restore_file_configured())
+ if (!restore_wal_segment_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- if (!shell_restore_file_configured())
+ if (!restore_timeline_history_configured())
goto not_available;
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- if (!shell_restore_file_configured())
+ if (!timeline_history_exists_configured())
goto not_available;
break;
}
@@ -174,14 +181,17 @@ RestoreArchivedFile(char *path, const char *xlogfname,
switch (archive_type)
{
case ARCHIVE_TYPE_WAL_SEGMENT:
- ret = shell_restore_wal_segment(xlogfname, xlogpath,
- lastRestartPointFname);
+ ret = RestoreCallbacks->restore_wal_segment_cb(restore_module_state,
+ xlogfname, xlogpath,
+ lastRestartPointFname);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->restore_timeline_history_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
case ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS:
- ret = shell_restore_timeline_history(xlogfname, xlogpath);
+ ret = RestoreCallbacks->timeline_history_exists_cb(restore_module_state,
+ xlogfname, xlogpath);
break;
}
@@ -189,6 +199,16 @@ RestoreArchivedFile(char *path, const char *xlogfname,
if (ret)
{
+ /*
+ * Some restore functions might not copy the file (e.g., checking
+ * whether a timeline history file exists), but they can set a flag to
+ * tell us if they do. We only need to verify file existence if this
+ * flag is enabled.
+ */
+ if (archive_type == ARCHIVE_TYPE_TIMELINE_HISTORY_EXISTS &&
+ !restore_module_state->timeline_history_exists_cb_copies)
+ return true;
+
/*
* command apparently succeeded, but let's make sure the file is
* really there now and has the correct size.
@@ -630,3 +650,65 @@ XLogArchiveCleanup(const char *xlog)
unlink(archiveStatusPath);
/* should we complain about failure? */
}
+
+/*
+ * Loads all the restore callbacks into our global RestoreCallbacks. The
+ * caller is responsible for validating the combination of library/command
+ * parameters that are set (e.g., restore_command and restore_library cannot
+ * both be set).
+ */
+void
+LoadRestoreCallbacks(void)
+{
+ RestoreModuleInit init;
+
+ /*
+ * If the shell command is enabled, use our special initialization
+ * function. Otherwise, load the library and call its
+ * _PG_restore_module_init().
+ */
+ if (restoreLibrary[0] == '\0')
+ init = shell_restore_init;
+ else
+ init = (RestoreModuleInit)
+ load_external_function(restoreLibrary, "_PG_restore_module_init",
+ false, NULL);
+
+ if (init == NULL)
+ ereport(ERROR,
+ (errmsg("restore modules have to define the symbol "
+ "_PG_restore_module_init")));
+
+ RestoreCallbacks = (*init) ();
+
+ /* restore state should be freed before calling this function */
+ Assert(restore_module_state == NULL);
+ restore_module_state = (RestoreModuleState *)
+ MemoryContextAllocZero(TopMemoryContext,
+ sizeof(RestoreModuleState));
+
+ if (RestoreCallbacks->startup_cb != NULL)
+ RestoreCallbacks->startup_cb(restore_module_state);
+}
+
+/*
+ * Call the shutdown callback of the loaded restore module, if defined. Also,
+ * free the restore module state if it was allocated.
+ *
+ * Processes that load restore libraries should register this via
+ * before_shmem_exit().
+ */
+void
+call_restore_module_shutdown_cb(int code, Datum arg)
+{
+ if (RestoreCallbacks != NULL &&
+ RestoreCallbacks->shutdown_cb != NULL &&
+ restore_module_state != NULL)
+ RestoreCallbacks->shutdown_cb(restore_module_state);
+
+ if (restore_module_state != NULL)
+ {
+ pfree(restore_module_state);
+ restore_module_state = NULL;
+ }
+}
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4af31d5739..2afdbddfd1 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -51,6 +51,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/walreceiver.h"
+#include "restore/restore_module.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/latch.h"
@@ -1117,18 +1118,21 @@ validateRecoveryParameters(void)
if (StandbyModeRequested)
{
if ((PrimaryConnInfo == NULL || strcmp(PrimaryConnInfo, "") == 0) &&
- (recoveryRestoreCommand == NULL || strcmp(recoveryRestoreCommand, "") == 0))
+ (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured()))
ereport(WARNING,
- (errmsg("specified neither \"primary_conninfo\" nor \"restore_command\""),
+ (errmsg("specified neither \"primary_conninfo\" nor \"restore_command\" nor a configured \"restore_library\""),
errhint("The database server will regularly poll the pg_wal subdirectory to check for files placed there.")));
}
else
{
- if (recoveryRestoreCommand == NULL ||
- strcmp(recoveryRestoreCommand, "") == 0)
+ if (!restore_wal_segment_configured() ||
+ !restore_timeline_history_configured() ||
+ !timeline_history_exists_configured())
ereport(FATAL,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("must specify \"restore_command\" when standby mode is not enabled")));
+ errmsg("must specify \"restore_command\" or a configured \"restore_library\" when standby mode is not enabled")));
}
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 3c68a9904d..e8769e9309 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -46,6 +46,7 @@
#include "postmaster/bgwriter.h"
#include "postmaster/interrupt.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/condition_variable.h"
#include "storage/fd.h"
@@ -230,6 +231,25 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(checkpointer_context);
+ /*
+ * Load restore_library so that we can use its archive-cleanup callback,
+ * if one is defined.
+ *
+ * We also take this opportunity to set up the before_shmem_exit hook for
+ * the shutdown callback and to check that only one of restore_library,
+ * archive_cleanup_command is set. Note that we emit a WARNING upon
+ * detecting misconfigured parameters, and we clear the callbacks so that
+ * the archive-cleanup functionality is skipped. We judge this
+ * functionality to not be important enough to require blocking
+ * checkpoints or shutting down the server when the parameters are
+ * misconfigured.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING))
+ LoadRestoreCallbacks();
+
/*
* If an exception is encountered, processing resumes here.
*
@@ -562,6 +582,10 @@ HandleCheckpointerInterrupts(void)
if (ConfigReloadPending)
{
+ bool archive_cleanup_settings_changed = false;
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevArchiveCleanupCommand = pstrdup(archiveCleanupCommand);
+
ConfigReloadPending = false;
ProcessConfigFile(PGC_SIGHUP);
@@ -577,6 +601,36 @@ HandleCheckpointerInterrupts(void)
* because of SIGHUP.
*/
UpdateSharedMemoryConfig();
+
+ /*
+ * If the archive-cleanup settings have changed, call the currently
+ * loaded shutdown callback and clear all the restore callbacks.
+ * There's presently no good way to unload a library besides
+ * restarting the process, and there should be little harm in leaving
+ * it around, so we just leave it loaded.
+ */
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevArchiveCleanupCommand, archiveCleanupCommand) != 0)
+ {
+ archive_cleanup_settings_changed = true;
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ RestoreCallbacks = NULL;
+ }
+
+ /*
+ * As in CheckpointerMain(), we only emit a WARNING if we detect that
+ * both restore_library and archive_cleanup_command are set. We do
+ * this even if the archive-cleanup settings haven't changed to remind
+ * the user about the misconfiguration.
+ */
+ if (CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ archiveCleanupCommand, "archive_cleanup_command",
+ WARNING) &&
+ archive_cleanup_settings_changed)
+ LoadRestoreCallbacks();
+
+ pfree(prevRestoreLibrary);
+ pfree(prevArchiveCleanupCommand);
}
if (ShutdownRequestPending)
{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cc665ce259..f8d3d6b3d2 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
#include "miscadmin.h"
#include "postmaster/auxprocess.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "storage/ipc.h"
#include "storage/pmsignal.h"
#include "storage/procsignal.h"
@@ -119,13 +120,17 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
* Re-read the config file.
*
* If one of the critical walreceiver options has changed, flag xlog.c
- * to restart it.
+ * to restart it. Also, check for invalid combinations of the command/library
+ * parameters and reload the restore callbacks if necessary.
*/
static void
StartupRereadConfig(void)
{
char *conninfo = pstrdup(PrimaryConnInfo);
char *slotname = pstrdup(PrimarySlotName);
+ char *prevRestoreLibrary = pstrdup(restoreLibrary);
+ char *prevRestoreCommand = pstrdup(recoveryRestoreCommand);
+ char *prevRecoveryEndCommand = pstrdup(recoveryEndCommand);
bool tempSlot = wal_receiver_create_temp_slot;
bool conninfoChanged;
bool slotnameChanged;
@@ -147,6 +152,30 @@ StartupRereadConfig(void)
if (conninfoChanged || slotnameChanged || tempSlotChanged)
StartupRequestWalReceiverRestart();
+
+ /*
+ * If the restore settings have changed, call the currently loaded
+ * shutdown callback and load the new callbacks. There's presently no
+ * good way to unload a library besides restarting the process, and there
+ * should be little harm in leaving it around, so we just leave it loaded.
+ */
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ if (strcmp(prevRestoreLibrary, restoreLibrary) != 0 ||
+ strcmp(prevRestoreCommand, recoveryRestoreCommand) != 0 ||
+ strcmp(prevRecoveryEndCommand, recoveryEndCommand) != 0)
+ {
+ call_restore_module_shutdown_cb(0, (Datum) 0);
+ LoadRestoreCallbacks();
+ }
+
+ pfree(prevRestoreLibrary);
+ pfree(prevRestoreCommand);
+ pfree(prevRecoveryEndCommand);
}
/* Handle various signals that might be sent to the startup process */
@@ -261,6 +290,19 @@ StartupProcessMain(char *startup_data, size_t startup_data_len)
*/
sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
+ /*
+ * Check for invalid combinations of the command/library parameters and
+ * load the callbacks.
+ */
+ before_shmem_exit(call_restore_module_shutdown_cb, 0);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryRestoreCommand, "restore_command",
+ ERROR);
+ (void) CheckMutuallyExclusiveStringGUCs(restoreLibrary, "restore_library",
+ recoveryEndCommand, "recovery_end_command",
+ ERROR);
+ LoadRestoreCallbacks();
+
/*
* Do what we came for.
*/
diff --git a/src/backend/restore/shell_restore.c b/src/backend/restore/shell_restore.c
index 42291625c1..be25f04044 100644
--- a/src/backend/restore/shell_restore.c
+++ b/src/backend/restore/shell_restore.c
@@ -3,7 +3,8 @@
* shell_restore.c
*
* These restore functions use a user-specified shell command (e.g., the
- * restore_command GUC).
+ * restore_command GUC). It is used as the default, but other modules may
+ * define their own restore logic.
*
* Copyright (c) 2024, PostgreSQL Global Development Group
*
@@ -22,25 +23,76 @@
#include "common/archive.h"
#include "common/percentrepl.h"
#include "postmaster/startup.h"
+#include "restore/restore_module.h"
#include "restore/shell_restore.h"
#include "storage/ipc.h"
#include "utils/wait_event.h"
+static void shell_restore_startup(RestoreModuleState *state);
+static bool shell_restore_file_configured(RestoreModuleState *state);
static bool shell_restore_file(const char *file, const char *path,
const char *lastRestartPointFileName);
+static bool shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+static bool shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path);
+static bool shell_archive_cleanup_configured(RestoreModuleState *state);
+static void shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+static bool shell_recovery_end_configured(RestoreModuleState *state);
+static void shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName);
static void ExecuteRecoveryCommand(const char *command,
const char *commandName,
bool failOnSignal,
uint32 wait_event_info,
const char *lastRestartPointFileName);
+static const RestoreModuleCallbacks shell_restore_callbacks = {
+ .startup_cb = shell_restore_startup,
+ .restore_wal_segment_configured_cb = shell_restore_file_configured,
+ .restore_wal_segment_cb = shell_restore_wal_segment,
+ .restore_timeline_history_configured_cb = shell_restore_file_configured,
+ .restore_timeline_history_cb = shell_restore_timeline_history,
+ .timeline_history_exists_configured_cb = shell_restore_file_configured,
+ .timeline_history_exists_cb = shell_restore_timeline_history,
+ .archive_cleanup_configured_cb = shell_archive_cleanup_configured,
+ .archive_cleanup_cb = shell_archive_cleanup,
+ .recovery_end_configured_cb = shell_recovery_end_configured,
+ .recovery_end_cb = shell_recovery_end,
+ .shutdown_cb = NULL
+};
+
+/*
+ * shell_restore_init
+ *
+ * Returns the callbacks for restoring via shell.
+ */
+const RestoreModuleCallbacks *
+shell_restore_init(void)
+{
+ return &shell_restore_callbacks;
+}
+
+/*
+ * Indicates that our timeline_history_exists_cb only attempts to retrieve the
+ * file, so the caller should verify the file exists afterwards.
+ */
+static void
+shell_restore_startup(RestoreModuleState *state)
+{
+ state->timeline_history_exists_cb_copies = true;
+}
+
/*
* Attempt to execute a shell-based restore command to retrieve a WAL segment.
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_wal_segment(const char *file, const char *path,
+static bool
+shell_restore_wal_segment(RestoreModuleState *state,
+ const char *file, const char *path,
const char *lastRestartPointFileName)
{
return shell_restore_file(file, path, lastRestartPointFileName);
@@ -52,8 +104,9 @@ shell_restore_wal_segment(const char *file, const char *path,
*
* Returns true if the command has succeeded, false otherwise.
*/
-bool
-shell_restore_timeline_history(const char *file, const char *path)
+static bool
+shell_restore_timeline_history(RestoreModuleState *state,
+ const char *file, const char *path)
{
char lastRestartPointFname[MAXPGPATH];
@@ -64,8 +117,8 @@ shell_restore_timeline_history(const char *file, const char *path)
/*
* Check whether restore_command is supplied.
*/
-bool
-shell_restore_file_configured(void)
+static bool
+shell_restore_file_configured(RestoreModuleState *state)
{
return recoveryRestoreCommand[0] != '\0';
}
@@ -152,8 +205,8 @@ shell_restore_file(const char *file, const char *path,
/*
* Check whether archive_cleanup_command is supplied.
*/
-bool
-shell_archive_cleanup_configured(void)
+static bool
+shell_archive_cleanup_configured(RestoreModuleState *state)
{
return archiveCleanupCommand[0] != '\0';
}
@@ -161,8 +214,9 @@ shell_archive_cleanup_configured(void)
/*
* Attempt to execute a shell-based archive cleanup command.
*/
-void
-shell_archive_cleanup(const char *lastRestartPointFileName)
+static void
+shell_archive_cleanup(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(archiveCleanupCommand, "archive_cleanup_command",
false, WAIT_EVENT_ARCHIVE_CLEANUP_COMMAND,
@@ -172,8 +226,8 @@ shell_archive_cleanup(const char *lastRestartPointFileName)
/*
* Check whether recovery_end_command is supplied.
*/
-bool
-shell_recovery_end_configured(void)
+static bool
+shell_recovery_end_configured(RestoreModuleState *state)
{
return recoveryEndCommand[0] != '\0';
}
@@ -181,8 +235,9 @@ shell_recovery_end_configured(void)
/*
* Attempt to execute a shell-based end-of-recovery command.
*/
-void
-shell_recovery_end(const char *lastRestartPointFileName)
+static void
+shell_recovery_end(RestoreModuleState *state,
+ const char *lastRestartPointFileName)
{
ExecuteRecoveryCommand(recoveryEndCommand, "recovery_end_command", true,
WAIT_EVENT_RECOVERY_END_COMMAND,
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 46c258be28..8265d57c4d 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -70,6 +70,7 @@
#include "replication/slot.h"
#include "replication/slotsync.h"
#include "replication/syncrep.h"
+#include "restore/restore_module.h"
#include "storage/bufmgr.h"
#include "storage/large_object.h"
#include "storage/pg_shmem.h"
@@ -3967,6 +3968,16 @@ struct config_string ConfigureNamesString[] =
NULL, NULL, NULL
},
+ {
+ {"restore_library", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
+ gettext_noop("Sets the library that will be called for restore actions."),
+ NULL
+ },
+ &restoreLibrary,
+ "",
+ NULL, NULL, NULL
+ },
+
{
{"archive_cleanup_command", PGC_SIGHUP, WAL_ARCHIVE_RECOVERY,
gettext_noop("Sets the shell command that will be executed at every restart point."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 83d5df8e46..a9a2475c4b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -285,6 +285,7 @@
# placeholders: %p = path of file to restore
# %f = file name only
# e.g. 'cp /mnt/server/archivedir/%f %p'
+#restore_library = '' # library to use for restore actions
#archive_cleanup_command = '' # command to execute at every restartpoint
#recovery_end_command = '' # command to execute at completion of recovery
diff --git a/src/include/restore/restore_module.h b/src/include/restore/restore_module.h
new file mode 100644
index 0000000000..cc148e855a
--- /dev/null
+++ b/src/include/restore/restore_module.h
@@ -0,0 +1,143 @@
+/*-------------------------------------------------------------------------
+ *
+ * restore_module.h
+ * Exports for restore modules.
+ *
+ * Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ * src/include/restore/restore_module.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef _RESTORE_MODULE_H
+#define _RESTORE_MODULE_H
+
+/*
+ * The value of the restore_library GUC.
+ */
+extern PGDLLIMPORT char *restoreLibrary;
+
+typedef struct RestoreModuleState
+{
+ /*
+ * Private data pointer for use by a restore module. This can be used to
+ * store state for the module that will be passed to each of its
+ * callbacks.
+ */
+ void *private_data;
+
+ /*
+ * Indicates whether the callback that checks for timeline existence
+ * merely copies the file. If set to true, the server will verify that
+ * the timeline history file exists after the callback returns.
+ */
+ bool timeline_history_exists_cb_copies;
+} RestoreModuleState;
+
+/*
+ * Restore module callbacks
+ *
+ * These callback functions should be defined by restore libraries and returned
+ * via _PG_restore_module_init(). For more information about the purpose of
+ * each callback, refer to the restore modules documentation.
+ */
+typedef void (*RestoreStartupCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreWalSegmentCB) (RestoreModuleState *state,
+ const char *file, const char *path,
+ const char *lastRestartPointFileName);
+typedef bool (*RestoreTimelineHistoryConfiguredCB) (RestoreModuleState *state);
+typedef bool (*RestoreTimelineHistoryCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*TimelineHistoryExistsConfiguredCB) (RestoreModuleState *state);
+typedef bool (*TimelineHistoryExistsCB) (RestoreModuleState *state,
+ const char *file, const char *path);
+typedef bool (*ArchiveCleanupConfiguredCB) (RestoreModuleState *state);
+typedef void (*ArchiveCleanupCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef bool (*RecoveryEndConfiguredCB) (RestoreModuleState *state);
+typedef void (*RecoveryEndCB) (RestoreModuleState *state,
+ const char *lastRestartPointFileName);
+typedef void (*RestoreShutdownCB) (RestoreModuleState *state);
+
+typedef struct RestoreModuleCallbacks
+{
+ RestoreStartupCB startup_cb;
+ RestoreWalSegmentConfiguredCB restore_wal_segment_configured_cb;
+ RestoreWalSegmentCB restore_wal_segment_cb;
+ RestoreTimelineHistoryConfiguredCB restore_timeline_history_configured_cb;
+ RestoreTimelineHistoryCB restore_timeline_history_cb;
+ TimelineHistoryExistsConfiguredCB timeline_history_exists_configured_cb;
+ TimelineHistoryExistsCB timeline_history_exists_cb;
+ ArchiveCleanupConfiguredCB archive_cleanup_configured_cb;
+ ArchiveCleanupCB archive_cleanup_cb;
+ RecoveryEndConfiguredCB recovery_end_configured_cb;
+ RecoveryEndCB recovery_end_cb;
+ RestoreShutdownCB shutdown_cb;
+} RestoreModuleCallbacks;
+
+/*
+ * Type of the shared library symbol _PG_restore_module_init that is looked up
+ * when loading a restore library.
+ */
+typedef const RestoreModuleCallbacks *(*RestoreModuleInit) (void);
+
+extern PGDLLEXPORT const RestoreModuleCallbacks *_PG_restore_module_init(void);
+
+extern void LoadRestoreCallbacks(void);
+extern void call_restore_module_shutdown_cb(int code, Datum arg);
+
+extern const RestoreModuleCallbacks *RestoreCallbacks;
+extern RestoreModuleState *restore_module_state;
+
+/*
+ * The following *_configured() functions are convenient wrappers around the
+ * *_configured_cb() callback functions with additional defensive NULL checks.
+ */
+
+static inline bool
+restore_wal_segment_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_cb != NULL &&
+ RestoreCallbacks->restore_wal_segment_configured_cb(restore_module_state);
+}
+
+static inline bool
+restore_timeline_history_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_cb != NULL &&
+ RestoreCallbacks->restore_timeline_history_configured_cb(restore_module_state);
+}
+
+static inline bool
+timeline_history_exists_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_cb != NULL &&
+ RestoreCallbacks->timeline_history_exists_configured_cb(restore_module_state);
+}
+
+static inline bool
+archive_cleanup_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_cb != NULL &&
+ RestoreCallbacks->archive_cleanup_configured_cb(restore_module_state);
+}
+
+static inline bool
+recovery_end_configured(void)
+{
+ return RestoreCallbacks != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb != NULL &&
+ RestoreCallbacks->recovery_end_cb != NULL &&
+ RestoreCallbacks->recovery_end_configured_cb(restore_module_state);
+}
+
+#endif /* _RESTORE_MODULE_H */
diff --git a/src/include/restore/shell_restore.h b/src/include/restore/shell_restore.h
index 28b5b7bc92..6352623866 100644
--- a/src/include/restore/shell_restore.h
+++ b/src/include/restore/shell_restore.h
@@ -12,15 +12,13 @@
#ifndef _SHELL_RESTORE_H
#define _SHELL_RESTORE_H
-extern bool shell_restore_file_configured(void);
-extern bool shell_restore_wal_segment(const char *file, const char *path,
- const char *lastRestartPointFileName);
-extern bool shell_restore_timeline_history(const char *file, const char *path);
+#include "restore/restore_module.h"
-extern bool shell_archive_cleanup_configured(void);
-extern void shell_archive_cleanup(const char *lastRestartPointFileName);
-
-extern bool shell_recovery_end_configured(void);
-extern void shell_recovery_end(const char *lastRestartPointFileName);
+/*
+ * Since the logic for restoring via shell commands is in the core server and
+ * does not need to be loaded via a shared library, it has a special
+ * initialization function.
+ */
+extern const RestoreModuleCallbacks *shell_restore_init(void);
#endif /* _SHELL_RESTORE_H */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index b5e4608817..5859126c23 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2448,6 +2448,8 @@ ResourceReleaseCallback
ResourceReleaseCallbackItem
ResourceReleasePhase
ResourceReleasePriority
+RestoreModuleCallbacks
+RestoreModuleState
RestoreOptions
RestorePass
RestrictInfo
--
2.39.3 (Apple Git-146)