agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v4 1/3] Move the code to restore files via the shell to a separate file. 7+ messages / 2 participants [nested] [flat]
* [PATCH v4 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --Kj7319i9nmIyA2yE Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v5 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --C7zPtVaVf+AK4Oqc Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v5-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v2 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --vkogqOf2sHV7VnPd Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v3 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --KsGdsel6WgEHnImy Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v3-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v6 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --pWyiEgJYm5f9v55/ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH v1 1/3] Move the code to restore files via the shell to a separate file. @ 2022-12-24 00:35 Nathan Bossart <nathandbossart@gmail.com> 0 siblings, 0 replies; 7+ messages in thread From: Nathan Bossart @ 2022-12-24 00:35 UTC (permalink / raw) 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 --Qxx1br4bt0+wmkIi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0002-Refactor-code-for-restoring-files-via-shell.patch" ^ permalink raw reply [nested|flat] 7+ messages in thread
* [PATCH] Test to demonstrate bug in commit 0d3dba38c7 and to verify a fix. @ 2026-05-12 10:27 Antonin Houska <ah@cybertec.at> 0 siblings, 0 replies; 7+ messages in thread From: Antonin Houska @ 2026-05-12 10:27 UTC (permalink / raw) The fix: https://www.postgresql.org/message-id/77611.1778055944%40localhost --- src/backend/access/transam/xact.c | 2 + src/backend/commands/repack_worker.c | 2 + src/test/isolation/isolationtester.c | 9 +- .../expected/repack_running_xacts.out | 81 ++++++++++++ .../specs/repack_running_xacts.spec | 119 ++++++++++++++++++ 5 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 src/test/modules/injection_points/expected/repack_running_xacts.out create mode 100644 src/test/modules/injection_points/specs/repack_running_xacts.spec diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 5586fbe5b07..b63ee166028 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -65,6 +65,7 @@ #include "utils/builtins.h" #include "utils/combocid.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/relmapper.h" @@ -2428,6 +2429,7 @@ CommitTransaction(void) * must be done _before_ releasing locks we hold and _after_ * RecordTransactionCommit. */ + INJECTION_POINT("before-end-transaction", NULL); ProcArrayEndTransaction(MyProc, latestXid); /* diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c index c40f8c98e06..6835626d677 100644 --- a/src/backend/commands/repack_worker.c +++ b/src/backend/commands/repack_worker.c @@ -26,6 +26,7 @@ #include "storage/ipc.h" #include "storage/proc.h" #include "tcop/tcopprot.h" +#include "utils/injection_point.h" #include "utils/memutils.h" #define REPL_PLUGIN_NAME "pgrepack" @@ -233,6 +234,7 @@ repack_setup_logical_decoding(Oid relid) * Neither prepare_write nor do_write callback nor update_progress is * useful for us. */ + INJECTION_POINT("before-create-decoding-context", NULL); ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, NIL, true, diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c index 440c875b8ac..8f17ee412c9 100644 --- a/src/test/isolation/isolationtester.c +++ b/src/test/isolation/isolationtester.c @@ -216,15 +216,22 @@ main(int argc, char **argv) * exactly expect concurrent use of test tables. However, autovacuum will * occasionally take AccessExclusiveLock to truncate a table, and we must * ignore that transient wait. + * + * If the session's backend is blocked, and if its background worker is + * waiting on an injection point, we assume that the injection point is + * the reason for the backend to be blocked. That's what we check in the + * second query of the UNION. XXX Should we use a separate query for that? */ initPQExpBuffer(&wait_query); appendPQExpBufferStr(&wait_query, + "WITH blocking(res) AS (" "SELECT pg_catalog.pg_isolation_test_session_is_blocked($1, '{"); /* The spec syntax requires at least one session; assume that here. */ appendPQExpBufferStr(&wait_query, conns[1].backend_pid_str); for (i = 2; i < nconns; i++) appendPQExpBuffer(&wait_query, ",%s", conns[i].backend_pid_str); - appendPQExpBufferStr(&wait_query, "}')"); + appendPQExpBufferStr(&wait_query, "}') UNION " + "SELECT pg_catalog.pg_isolation_test_session_is_blocked(pid, '{}') FROM pg_stat_activity WHERE leader_pid=$1) SELECT bool_or(res) FROM blocking"); res = PQprepare(conns[0].conn, PREP_WAITING, wait_query.data, 0, NULL); if (PQresultStatus(res) != PGRES_COMMAND_OK) diff --git a/src/test/modules/injection_points/expected/repack_running_xacts.out b/src/test/modules/injection_points/expected/repack_running_xacts.out new file mode 100644 index 00000000000..271fe2b97cb --- /dev/null +++ b/src/test/modules/injection_points/expected/repack_running_xacts.out @@ -0,0 +1,81 @@ +Parsed test spec with 5 sessions + +starting permutation: repack s3_assign_xid wakeup_bcdc s4_changes s4_attach s4_commit s3_commit s5_assign_xid wakeup_bet s5_commit check +injection_points_attach +----------------------- + +(1 row) + +step repack: + REPACK (CONCURRENTLY) repack_test; + <waiting ...> +step s3_assign_xid: + BEGIN; + INSERT INTO aux VALUES (1); + +step wakeup_bcdc: + SELECT injection_points_wakeup('before-create-decoding-context'); + +injection_points_wakeup +----------------------- + +(1 row) + +step s4_changes: + BEGIN; + INSERT INTO repack_test(i, j) VALUES (1, 1); + +step s4_attach: + SELECT injection_points_set_local(); + SELECT injection_points_attach('before-end-transaction', 'wait'); + +injection_points_set_local +-------------------------- + +(1 row) + +injection_points_attach +----------------------- + +(1 row) + +step s4_commit: + COMMIT; + <waiting ...> +step s3_commit: + COMMIT; + +step s5_assign_xid: + BEGIN; + INSERT INTO aux VALUES (2); + +step wakeup_bet: + SELECT injection_points_wakeup('before-end-transaction'); + +injection_points_wakeup +----------------------- + +(1 row) + +step repack: <... completed> +step s4_commit: <... completed> +step s5_commit: + COMMIT; + +step check: + TABLE repack_test; + +i|j +-+- +(0 rows) + +injection_points_detach +----------------------- + +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/specs/repack_running_xacts.spec b/src/test/modules/injection_points/specs/repack_running_xacts.spec new file mode 100644 index 00000000000..1f878514046 --- /dev/null +++ b/src/test/modules/injection_points/specs/repack_running_xacts.spec @@ -0,0 +1,119 @@ +setup +{ + CREATE EXTENSION injection_points; + CREATE TABLE repack_test(i int PRIMARY KEY, j int); + CREATE TABLE aux(i int); +} + +teardown +{ + DROP TABLE repack_test; + DROP TABLE aux; + DROP EXTENSION injection_points; +} + +session s1 +setup +{ + SELECT injection_points_attach('before-create-decoding-context', 'wait'); +} +step repack +{ + REPACK (CONCURRENTLY) repack_test; +} +step check +{ + TABLE repack_test; +} +teardown +{ + SELECT injection_points_detach('before-create-decoding-context'); +} + +session s2 +step wakeup_bcdc +{ + SELECT injection_points_wakeup('before-create-decoding-context'); +} +step wakeup_bet +{ + SELECT injection_points_wakeup('before-end-transaction'); +} + +session s3 +step s3_assign_xid +{ + BEGIN; + INSERT INTO aux VALUES (1); +} +step s3_commit +{ + COMMIT; +} + +session s4 +step s4_changes +{ + BEGIN; + INSERT INTO repack_test(i, j) VALUES (1, 1); +} +# Do not attach in the setup section, that would be too soon. +step s4_attach +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('before-end-transaction', 'wait'); +} +step s4_commit +{ + COMMIT; +} +teardown +{ + SELECT injection_points_detach('before-end-transaction'); +} + +session s5 +step s5_assign_xid +{ + BEGIN; + INSERT INTO aux VALUES (2); +} +step s5_commit +{ + COMMIT; +} + +permutation +repack +# Assign XID so that a running transaction prevents the snapshot builder from +# reaching CONSISTENT state immediately. It will wait for this to complete +# after having reached BUILDING_SNAPSHOT. +s3_assign_xid +# Let the decoding setup start. +wakeup_bcdc +# Likewise, the snapshot builder will wait for the s4's xact to complete after +# having reached FULL_SNAPSHOT. This is the problematic transaction, so let it +# do some changes. +s4_changes +# Attach to the 'before-end-transaction' injection point that s4 will need +# during commit. +s4_attach +# Only write commit record for s4, but do not remove the xact from procarray +# yet. Thus the snapshot builder still needs to wait. +s4_commit +# Let the snapshot builder proceed to FULL_SNAPSHOT. +s3_commit +# Start another transaction so that CONSISTENT is not reached "directly", +# i.e. due to no running transaction. It's important here that builder->xmin +# does not advance. +s5_assign_xid +# Remove s4 xact from procarray, and thus reach the CONSISTENT state. Since +# the COMMIT appeared in WAL too early (i.e. when the snapshot builder state +# did not allow decoding of COMMIT records yet), the snapshot builder will +# consider s4 running. This is also due to returning from +# SnapBuildProcessRunningXacts() too early, w/o advancing builder->xmin. +wakeup_bet +# s5 is not needed anymore +s5_commit +# Show that the data changes performed by s4 are lost. +check -- 2.47.3 --=-=-=-- ^ permalink raw reply [nested|flat] 7+ messages in thread
end of thread, other threads:[~2026-05-12 10:27 UTC | newest] Thread overview: 7+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-12-24 00:35 [PATCH v1 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2022-12-24 00:35 [PATCH v4 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2022-12-24 00:35 [PATCH v5 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2022-12-24 00:35 [PATCH v2 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2022-12-24 00:35 [PATCH v3 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2022-12-24 00:35 [PATCH v6 1/3] Move the code to restore files via the shell to a separate file. Nathan Bossart <nathandbossart@gmail.com> 2026-05-12 10:27 [PATCH] Test to demonstrate bug in commit 0d3dba38c7 and to verify a fix. Antonin Houska <ah@cybertec.at>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox