From 35739b87dcfef9fc0186aca659f262746fecd778 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 17 Feb 2023 14:26:42 +0900 Subject: Redesign archive modules A new callback named startup_cb, called shortly after a module is loaded, is added. This makes possible the initialization of any additional state data required by a module. This initial state data can be saved in a ArchiveModuleState, that is now passed down to all the callbacks that can be defined in a module. With this design, it is possible to have a per-module state, aimed at opening the door to the support of more than one archive module. The initialization of the callbacks is changed so as _PG_archive_module_init() does not anymore give in input a ArchiveModuleCallbacks that a module has to fill in with callback definitions. Instead, a module now needs to return a const ArchiveModuleCallbacks. All the structure and callback definitions of archive modules are moved into their own header, named archive_module.h, from pgarch.h. Command-based archiving follows the same line, with a new set of files named shell_archive.{c,h}. There are a few more items that are under discussion to improve the design of archive modules, like the fact that basic_archive calls sigsetjmp() by itself to define its own error handling flow. These will be adjusted later, the changes done here cover already a good portion of what has been discussed. Any modules created for v15 will need to be adjusted to this new design. Author: Nathan Bossart Reviewed-by: Andres Freund Discussion: https://postgr.es/m/20230130194810.6fztfgbn32e7qarj@awork3.anarazel.de --- src/backend/Makefile | 2 +- src/backend/archive/Makefile | 18 +++++ src/backend/archive/meson.build | 5 ++ src/backend/archive/shell_archive.c | 140 +++++++++++++++++++++++++++++++++ src/backend/meson.build | 1 + src/backend/postmaster/Makefile | 1 - src/backend/postmaster/meson.build | 1 - src/backend/postmaster/pgarch.c | 27 ++++--- src/backend/postmaster/shell_archive.c | 129 ------------------------------ src/backend/utils/misc/guc_tables.c | 1 + src/include/archive/archive_module.h | 59 ++++++++++++++ src/include/archive/shell_archive.h | 24 ++++++ src/include/postmaster/pgarch.h | 39 --------- src/tools/pgindent/typedefs.list | 1 + 14 files changed, 266 insertions(+), 182 deletions(-) create mode 100644 src/backend/archive/Makefile create mode 100644 src/backend/archive/meson.build create mode 100644 src/backend/archive/shell_archive.c delete mode 100644 src/backend/postmaster/shell_archive.c create mode 100644 src/include/archive/archive_module.h create mode 100644 src/include/archive/shell_archive.h (limited to 'src') diff --git a/src/backend/Makefile b/src/backend/Makefile index 86e6dcb7925..e4bf0fe9c0e 100644 --- a/src/backend/Makefile +++ b/src/backend/Makefile @@ -17,7 +17,7 @@ subdir = src/backend top_builddir = ../.. include $(top_builddir)/src/Makefile.global -SUBDIRS = access backup bootstrap catalog parser commands executor \ +SUBDIRS = access archive backup bootstrap catalog parser commands executor \ foreign lib libpq \ main nodes optimizer partitioning port postmaster \ regex replication rewrite \ diff --git a/src/backend/archive/Makefile b/src/backend/archive/Makefile new file mode 100644 index 00000000000..8d2860d0dfa --- /dev/null +++ b/src/backend/archive/Makefile @@ -0,0 +1,18 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for src/backend/archive +# +# IDENTIFICATION +# src/backend/archive/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/backend/archive +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + shell_archive.o + +include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/archive/meson.build b/src/backend/archive/meson.build new file mode 100644 index 00000000000..e194282931c --- /dev/null +++ b/src/backend/archive/meson.build @@ -0,0 +1,5 @@ +# Copyright (c) 2023, PostgreSQL Global Development Group + +backend_sources += files( + 'shell_archive.c' +) diff --git a/src/backend/archive/shell_archive.c b/src/backend/archive/shell_archive.c new file mode 100644 index 00000000000..157ca4c751b --- /dev/null +++ b/src/backend/archive/shell_archive.c @@ -0,0 +1,140 @@ +/*------------------------------------------------------------------------- + * + * shell_archive.c + * + * This archiving function uses a user-specified shell command (the + * archive_command GUC) to copy write-ahead log files. It is used as the + * default, but other modules may define their own custom archiving logic. + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/archive/shell_archive.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include + +#include "access/xlog.h" +#include "archive/archive_module.h" +#include "archive/shell_archive.h" +#include "common/percentrepl.h" +#include "pgstat.h" + +static bool shell_archive_configured(ArchiveModuleState *state); +static bool shell_archive_file(ArchiveModuleState *state, + const char *file, + const char *path); +static void shell_archive_shutdown(ArchiveModuleState *state); + +static const ArchiveModuleCallbacks shell_archive_callbacks = { + .startup_cb = NULL, + .check_configured_cb = shell_archive_configured, + .archive_file_cb = shell_archive_file, + .shutdown_cb = shell_archive_shutdown +}; + +const ArchiveModuleCallbacks * +shell_archive_init(void) +{ + return &shell_archive_callbacks; +} + +static bool +shell_archive_configured(ArchiveModuleState *state) +{ + return XLogArchiveCommand[0] != '\0'; +} + +static bool +shell_archive_file(ArchiveModuleState *state, const char *file, + const char *path) +{ + char *xlogarchcmd; + char *nativePath = NULL; + int rc; + + if (path) + { + nativePath = pstrdup(path); + make_native_path(nativePath); + } + + xlogarchcmd = replace_percent_placeholders(XLogArchiveCommand, + "archive_command", "fp", + file, nativePath); + + if (nativePath) + pfree(nativePath); + + ereport(DEBUG3, + (errmsg_internal("executing archive command \"%s\"", + xlogarchcmd))); + + fflush(NULL); + pgstat_report_wait_start(WAIT_EVENT_ARCHIVE_COMMAND); + rc = system(xlogarchcmd); + pgstat_report_wait_end(); + + if (rc != 0) + { + /* + * If either the shell itself, or a called command, died on a signal, + * abort the archiver. We do this because system() ignores SIGINT and + * SIGQUIT while waiting; so a signal is very likely something that + * should have interrupted us too. Also die if the shell got a hard + * "command not found" type of error. If we overreact it's no big + * deal, the postmaster will just start the archiver again. + */ + int lev = wait_result_is_any_signal(rc, true) ? FATAL : LOG; + + if (WIFEXITED(rc)) + { + ereport(lev, + (errmsg("archive command failed with exit code %d", + WEXITSTATUS(rc)), + errdetail("The failed archive command was: %s", + xlogarchcmd))); + } + else if (WIFSIGNALED(rc)) + { +#if defined(WIN32) + ereport(lev, + (errmsg("archive command was terminated by exception 0x%X", + WTERMSIG(rc)), + errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."), + errdetail("The failed archive command was: %s", + xlogarchcmd))); +#else + ereport(lev, + (errmsg("archive command was terminated by signal %d: %s", + WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))), + errdetail("The failed archive command was: %s", + xlogarchcmd))); +#endif + } + else + { + ereport(lev, + (errmsg("archive command exited with unrecognized status %d", + rc), + errdetail("The failed archive command was: %s", + xlogarchcmd))); + } + + return false; + } + + pfree(xlogarchcmd); + + elog(DEBUG1, "archived write-ahead log file \"%s\"", file); + return true; +} + +static void +shell_archive_shutdown(ArchiveModuleState *state) +{ + elog(DEBUG1, "archiver process shutting down"); +} diff --git a/src/backend/meson.build b/src/backend/meson.build index b1db3ba75ba..4fdd209b826 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -7,6 +7,7 @@ backend_link_with = [pgport_srv, common_srv] generated_backend_sources = [] subdir('access') +subdir('archive') subdir('backup') subdir('bootstrap') subdir('catalog') diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d60..047448b34eb 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -22,7 +22,6 @@ OBJS = \ interrupt.o \ pgarch.o \ postmaster.o \ - shell_archive.o \ startup.o \ syslogger.o \ walwriter.o diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 9079922de7a..cda921fd10b 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -10,7 +10,6 @@ backend_sources += files( 'interrupt.c', 'pgarch.c', 'postmaster.c', - 'shell_archive.c', 'startup.c', 'syslogger.c', 'walwriter.c', diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index e551af29052..46af3495644 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -31,6 +31,8 @@ #include "access/xlog.h" #include "access/xlog_internal.h" +#include "archive/archive_module.h" +#include "archive/shell_archive.h" #include "lib/binaryheap.h" #include "libpq/pqsignal.h" #include "pgstat.h" @@ -97,7 +99,8 @@ char *XLogArchiveLibrary = ""; */ static time_t last_sigterm_time = 0; static PgArchData *PgArch = NULL; -static ArchiveModuleCallbacks ArchiveContext; +static const ArchiveModuleCallbacks *ArchiveCallbacks; +static ArchiveModuleState *archive_module_state; /* @@ -406,8 +409,8 @@ pgarch_ArchiverCopyLoop(void) HandlePgArchInterrupts(); /* can't do anything if not configured ... */ - if (ArchiveContext.check_configured_cb != NULL && - !ArchiveContext.check_configured_cb()) + if (ArchiveCallbacks->check_configured_cb != NULL && + !ArchiveCallbacks->check_configured_cb(archive_module_state)) { ereport(WARNING, (errmsg("archive_mode enabled, yet archiving is not configured"))); @@ -508,7 +511,7 @@ pgarch_archiveXlog(char *xlog) snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog); set_ps_display(activitymsg); - ret = ArchiveContext.archive_file_cb(xlog, pathname); + ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname); if (ret) snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog); else @@ -814,7 +817,7 @@ HandlePgArchInterrupts(void) /* * LoadArchiveLibrary * - * Loads the archiving callbacks into our local ArchiveContext. + * Loads the archiving callbacks into our local ArchiveCallbacks. */ static void LoadArchiveLibrary(void) @@ -827,8 +830,6 @@ LoadArchiveLibrary(void) errmsg("both archive_command and archive_library set"), errdetail("Only one of archive_command, archive_library may be set."))); - memset(&ArchiveContext, 0, sizeof(ArchiveModuleCallbacks)); - /* * If shell archiving is enabled, use our special initialization function. * Otherwise, load the library and call its _PG_archive_module_init(). @@ -844,12 +845,16 @@ LoadArchiveLibrary(void) ereport(ERROR, (errmsg("archive modules have to define the symbol %s", "_PG_archive_module_init"))); - (*archive_init) (&ArchiveContext); + ArchiveCallbacks = (*archive_init) (); - if (ArchiveContext.archive_file_cb == NULL) + if (ArchiveCallbacks->archive_file_cb == NULL) ereport(ERROR, (errmsg("archive modules must register an archive callback"))); + archive_module_state = (ArchiveModuleState *) palloc0(sizeof(ArchiveModuleState)); + if (ArchiveCallbacks->startup_cb != NULL) + ArchiveCallbacks->startup_cb(archive_module_state); + before_shmem_exit(pgarch_call_module_shutdown_cb, 0); } @@ -859,6 +864,6 @@ LoadArchiveLibrary(void) static void pgarch_call_module_shutdown_cb(int code, Datum arg) { - if (ArchiveContext.shutdown_cb != NULL) - ArchiveContext.shutdown_cb(); + if (ArchiveCallbacks->shutdown_cb != NULL) + ArchiveCallbacks->shutdown_cb(archive_module_state); } diff --git a/src/backend/postmaster/shell_archive.c b/src/backend/postmaster/shell_archive.c deleted file mode 100644 index 7771b951b7b..00000000000 --- a/src/backend/postmaster/shell_archive.c +++ /dev/null @@ -1,129 +0,0 @@ -/*------------------------------------------------------------------------- - * - * shell_archive.c - * - * This archiving function uses a user-specified shell command (the - * archive_command GUC) to copy write-ahead log files. It is used as the - * default, but other modules may define their own custom archiving logic. - * - * Copyright (c) 2022-2023, PostgreSQL Global Development Group - * - * IDENTIFICATION - * src/backend/postmaster/shell_archive.c - * - *------------------------------------------------------------------------- - */ -#include "postgres.h" - -#include - -#include "access/xlog.h" -#include "common/percentrepl.h" -#include "pgstat.h" -#include "postmaster/pgarch.h" - -static bool shell_archive_configured(void); -static bool shell_archive_file(const char *file, const char *path); -static void shell_archive_shutdown(void); - -void -shell_archive_init(ArchiveModuleCallbacks *cb) -{ - cb->check_configured_cb = shell_archive_configured; - cb->archive_file_cb = shell_archive_file; - cb->shutdown_cb = shell_archive_shutdown; -} - -static bool -shell_archive_configured(void) -{ - return XLogArchiveCommand[0] != '\0'; -} - -static bool -shell_archive_file(const char *file, const char *path) -{ - char *xlogarchcmd; - char *nativePath = NULL; - int rc; - - if (path) - { - nativePath = pstrdup(path); - make_native_path(nativePath); - } - - xlogarchcmd = replace_percent_placeholders(XLogArchiveCommand, "archive_command", "fp", file, nativePath); - - if (nativePath) - pfree(nativePath); - - ereport(DEBUG3, - (errmsg_internal("executing archive command \"%s\"", - xlogarchcmd))); - - fflush(NULL); - pgstat_report_wait_start(WAIT_EVENT_ARCHIVE_COMMAND); - rc = system(xlogarchcmd); - pgstat_report_wait_end(); - - if (rc != 0) - { - /* - * If either the shell itself, or a called command, died on a signal, - * abort the archiver. We do this because system() ignores SIGINT and - * SIGQUIT while waiting; so a signal is very likely something that - * should have interrupted us too. Also die if the shell got a hard - * "command not found" type of error. If we overreact it's no big - * deal, the postmaster will just start the archiver again. - */ - int lev = wait_result_is_any_signal(rc, true) ? FATAL : LOG; - - if (WIFEXITED(rc)) - { - ereport(lev, - (errmsg("archive command failed with exit code %d", - WEXITSTATUS(rc)), - errdetail("The failed archive command was: %s", - xlogarchcmd))); - } - else if (WIFSIGNALED(rc)) - { -#if defined(WIN32) - ereport(lev, - (errmsg("archive command was terminated by exception 0x%X", - WTERMSIG(rc)), - errhint("See C include file \"ntstatus.h\" for a description of the hexadecimal value."), - errdetail("The failed archive command was: %s", - xlogarchcmd))); -#else - ereport(lev, - (errmsg("archive command was terminated by signal %d: %s", - WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))), - errdetail("The failed archive command was: %s", - xlogarchcmd))); -#endif - } - else - { - ereport(lev, - (errmsg("archive command exited with unrecognized status %d", - rc), - errdetail("The failed archive command was: %s", - xlogarchcmd))); - } - - return false; - } - - pfree(xlogarchcmd); - - elog(DEBUG1, "archived write-ahead log file \"%s\"", file); - return true; -} - -static void -shell_archive_shutdown(void) -{ - elog(DEBUG1, "archiver process shutting down"); -} diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 43b9d926600..1c0583fe267 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -33,6 +33,7 @@ #include "access/xlog_internal.h" #include "access/xlogprefetcher.h" #include "access/xlogrecovery.h" +#include "archive/archive_module.h" #include "catalog/namespace.h" #include "catalog/storage.h" #include "commands/async.h" diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h new file mode 100644 index 00000000000..679ce5a6dbd --- /dev/null +++ b/src/include/archive/archive_module.h @@ -0,0 +1,59 @@ +/*------------------------------------------------------------------------- + * + * archive_module.h + * Exports for archive modules. + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * src/include/archive/archive_module.h + * + *------------------------------------------------------------------------- + */ +#ifndef _ARCHIVE_MODULE_H +#define _ARCHIVE_MODULE_H + +/* + * The value of the archive_library GUC. + */ +extern PGDLLIMPORT char *XLogArchiveLibrary; + +typedef struct ArchiveModuleState +{ + /* + * Private data pointer for use by an archive module. This can be used to + * store state for the module that will be passed to each of its + * callbacks. + */ + void *private_data; +} ArchiveModuleState; + +/* + * Archive module callbacks + * + * These callback functions should be defined by archive libraries and returned + * via _PG_archive_module_init(). ArchiveFileCB is the only required callback. + * For more information about the purpose of each callback, refer to the + * archive modules documentation. + */ +typedef void (*ArchiveStartupCB) (ArchiveModuleState *state); +typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state); +typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path); +typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state); + +typedef struct ArchiveModuleCallbacks +{ + ArchiveStartupCB startup_cb; + ArchiveCheckConfiguredCB check_configured_cb; + ArchiveFileCB archive_file_cb; + ArchiveShutdownCB shutdown_cb; +} ArchiveModuleCallbacks; + +/* + * Type of the shared library symbol _PG_archive_module_init that is looked + * up when loading an archive library. + */ +typedef const ArchiveModuleCallbacks *(*ArchiveModuleInit) (void); + +extern PGDLLEXPORT const ArchiveModuleCallbacks *_PG_archive_module_init(void); + +#endif /* _ARCHIVE_MODULE_H */ diff --git a/src/include/archive/shell_archive.h b/src/include/archive/shell_archive.h new file mode 100644 index 00000000000..9de6f769f12 --- /dev/null +++ b/src/include/archive/shell_archive.h @@ -0,0 +1,24 @@ +/*------------------------------------------------------------------------- + * + * shell_archive.h + * Exports for archiving via shell. + * + * Copyright (c) 2022-2023, PostgreSQL Global Development Group + * + * src/include/archive/shell_archive.h + * + *------------------------------------------------------------------------- + */ +#ifndef _SHELL_ARCHIVE_H +#define _SHELL_ARCHIVE_H + +#include "archive/archive_module.h" + +/* + * Since the logic for archiving via a shell command is in the core server + * and does not need to be loaded via a shared library, it has a special + * initialization function. + */ +extern const ArchiveModuleCallbacks *shell_archive_init(void); + +#endif /* _SHELL_ARCHIVE_H */ diff --git a/src/include/postmaster/pgarch.h b/src/include/postmaster/pgarch.h index bcd51dfad67..3bd4fac71e5 100644 --- a/src/include/postmaster/pgarch.h +++ b/src/include/postmaster/pgarch.h @@ -33,43 +33,4 @@ extern void PgArchiverMain(void) pg_attribute_noreturn(); extern void PgArchWakeup(void); extern void PgArchForceDirScan(void); -/* - * The value of the archive_library GUC. - */ -extern PGDLLIMPORT char *XLogArchiveLibrary; - -/* - * Archive module callbacks - * - * These callback functions should be defined by archive libraries and returned - * via _PG_archive_module_init(). ArchiveFileCB is the only required callback. - * For more information about the purpose of each callback, refer to the - * archive modules documentation. - */ -typedef bool (*ArchiveCheckConfiguredCB) (void); -typedef bool (*ArchiveFileCB) (const char *file, const char *path); -typedef void (*ArchiveShutdownCB) (void); - -typedef struct ArchiveModuleCallbacks -{ - ArchiveCheckConfiguredCB check_configured_cb; - ArchiveFileCB archive_file_cb; - ArchiveShutdownCB shutdown_cb; -} ArchiveModuleCallbacks; - -/* - * Type of the shared library symbol _PG_archive_module_init that is looked - * up when loading an archive library. - */ -typedef void (*ArchiveModuleInit) (ArchiveModuleCallbacks *cb); - -extern PGDLLEXPORT void _PG_archive_module_init(ArchiveModuleCallbacks *cb); - -/* - * Since the logic for archiving via a shell command is in the core server - * and does not need to be loaded via a shared library, it has a special - * initialization function. - */ -extern void shell_archive_init(ArchiveModuleCallbacks *cb); - #endif /* _PGARCH_H */ diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 45fc5759ced..22ea42c16b5 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -128,6 +128,7 @@ ArchiveHandle ArchiveMode ArchiveModuleCallbacks ArchiveModuleInit +ArchiveModuleState ArchiveOpts ArchiveShutdownCB ArchiveStreamState -- cgit v1.2.3