0001-Add-support-for-multi-file-archiving-in-archive-modu.patch
text/x-patch
Filename: 0001-Add-support-for-multi-file-archiving-in-archive-modu.patch
Type: text/x-patch
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch 0001
Subject: Add support for multi-file archiving in archive modules
| File | + | − |
|---|---|---|
| contrib/basic_archive/basic_archive.c | 22 | 0 |
| src/backend/archive/shell_archive.c | 110 | 49 |
| src/backend/postmaster/pgarch.c | 203 | 97 |
| src/backend/utils/misc/guc_tables.c | 9 | 0 |
| src/include/archive/archive_module.h | 18 | 0 |
| src/test/perl/PostgreSQL/Test/Cluster.pm | 33 | 14 |
| src/test/recovery/t/002_archiving.pl | 1 | 0 |
From 08104dcdd3295a48827e1d58c4c2382620267f5d Mon Sep 17 00:00:00 2001
From: Stepan Neretin <slpmcf@gmail.com>
Date: Mon, 21 Jul 2025 14:13:51 +0700
Subject: [PATCH] Add support for multi-file archiving in archive modules
This patch introduces optional support for archiving multiple WAL files
at once via a new archive_files_cb callback in archive modules.
A new GUC archive_multi enables this behavior.
The shell and basic archive modules have been updated to support the new callback.
---
contrib/basic_archive/basic_archive.c | 22 ++
src/backend/archive/shell_archive.c | 159 ++++++++----
src/backend/postmaster/pgarch.c | 300 +++++++++++++++--------
src/backend/utils/misc/guc_tables.c | 9 +
src/include/archive/archive_module.h | 18 ++
src/test/perl/PostgreSQL/Test/Cluster.pm | 47 ++--
src/test/recovery/t/002_archiving.pl | 1 +
7 files changed, 396 insertions(+), 160 deletions(-)
diff --git a/contrib/basic_archive/basic_archive.c b/contrib/basic_archive/basic_archive.c
index 4a8b8c7ac29..0f009e510ec 100644
--- a/contrib/basic_archive/basic_archive.c
+++ b/contrib/basic_archive/basic_archive.c
@@ -46,6 +46,7 @@ static char *archive_directory = NULL;
static bool basic_archive_configured(ArchiveModuleState *state);
static bool basic_archive_file(ArchiveModuleState *state, const char *file, const char *path);
+static bool basic_archive_files(ArchiveModuleState *state, char **files, char **paths, int nfiles);
static bool check_archive_directory(char **newval, void **extra, GucSource source);
static bool compare_files(const char *file1, const char *file2);
@@ -53,6 +54,7 @@ static const ArchiveModuleCallbacks basic_archive_callbacks = {
.startup_cb = NULL,
.check_configured_cb = basic_archive_configured,
.archive_file_cb = basic_archive_file,
+ .archive_files_cb = basic_archive_files,
.shutdown_cb = NULL
};
@@ -229,6 +231,26 @@ basic_archive_file(ArchiveModuleState *state, const char *file, const char *path
return true;
}
+/*
++ * Archive multiple files.
++ */
+static bool
+basic_archive_files(ArchiveModuleState *state, char **files, char **paths, int nfiles)
+{
+ bool result = true;
+
+ for (int i = 0; i < nfiles; i++)
+ {
+ if (!basic_archive_file(state, files[i], paths[i]))
+ {
+ ereport(WARNING, (errmsg("failed to archive file \"%s\"", files[i])));
+ result = false;
+ }
+ }
+
+ return result;
+}
+
/*
* compare_files
*
diff --git a/src/backend/archive/shell_archive.c b/src/backend/archive/shell_archive.c
index 828723afe47..9e8bf95c6c4 100644
--- a/src/backend/archive/shell_archive.c
+++ b/src/backend/archive/shell_archive.c
@@ -27,12 +27,17 @@ static bool shell_archive_configured(ArchiveModuleState *state);
static bool shell_archive_file(ArchiveModuleState *state,
const char *file,
const char *path);
+static bool shell_archive_files(ArchiveModuleState *state,
+ char **files,
+ char **paths,
+ int nfiles);
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,
+ .archive_files_cb = shell_archive_files,
.shutdown_cb = shell_archive_shutdown
};
@@ -54,26 +59,10 @@ shell_archive_configured(ArchiveModuleState *state)
}
static bool
-shell_archive_file(ArchiveModuleState *state, const char *file,
- const char *path)
+run_archive_command(const char *xlogarchcmd, const char *context_info, int nfiles)
{
- 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);
-
- ereport(DEBUG3,
- (errmsg_internal("executing archive command \"%s\"",
- xlogarchcmd)));
+ int rc;
+ TimestampTz start_time = GetCurrentTimestamp();
fflush(NULL);
pgstat_report_wait_start(WAIT_EVENT_ARCHIVE_COMMAND);
@@ -82,59 +71,131 @@ shell_archive_file(ArchiveModuleState *state, const char *file,
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;
+ 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)));
+ (errmsg("archive command failed with exit code %d", WEXITSTATUS(rc)),
+ errdetail("The failed archive command was: %s", xlogarchcmd),
+ context_info ? errhint("%s", context_info) : 0));
}
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)));
+ (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),
+ context_info ? errhint("%s", context_info) : 0));
#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)));
+ (errmsg("archive command was terminated by signal %d: %s",
+ WTERMSIG(rc), pg_strsignal(WTERMSIG(rc))),
+ errdetail("The failed archive command was: %s", xlogarchcmd),
+ context_info ? errhint("%s", context_info) : 0));
#endif
}
else
{
ereport(lev,
- (errmsg("archive command exited with unrecognized status %d",
- rc),
- errdetail("The failed archive command was: %s",
- xlogarchcmd)));
+ (errmsg("archive command exited with unrecognized status %d", rc),
+ errdetail("The failed archive command was: %s", xlogarchcmd),
+ context_info ? errhint("%s", context_info) : 0));
}
- pfree(xlogarchcmd);
-
return false;
}
- pfree(xlogarchcmd);
- elog(DEBUG1, "archived write-ahead log file \"%s\"", file);
return true;
}
+static bool
+shell_archive_file(ArchiveModuleState *state, const char *file, const char *path)
+{
+ char *xlogarchcmd;
+ char *nativePath = NULL;
+ bool success;
+
+ if (path)
+ {
+ nativePath = pstrdup(path);
+ make_native_path(nativePath);
+ }
+
+ xlogarchcmd = replace_percent_placeholders(XLogArchiveCommand,
+ "archive_command", "fp",
+ file, nativePath);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing archive command \"%s\"", xlogarchcmd)));
+
+ success = run_archive_command(xlogarchcmd, file, 1);
+
+ if (success)
+ elog(DEBUG1, "archived write-ahead log file \"%s\"", file);
+
+ pfree(xlogarchcmd);
+ if (nativePath)
+ pfree(nativePath);
+
+ return success;
+}
+
+
+static bool
+shell_archive_files(ArchiveModuleState *state, char **files, char **paths, int nfiles)
+{
+ StringInfoData filebuf;
+ StringInfoData pathbuf;
+ char nfiles_str[16];
+ char *xlogarchcmd;
+ bool success;
+
+ initStringInfo(&filebuf);
+ for (int i = 0; i < nfiles; i++)
+ {
+ if (i > 0)
+ appendStringInfoChar(&filebuf, ' ');
+ appendStringInfoString(&filebuf, files[i]);
+ }
+
+ initStringInfo(&pathbuf);
+ for (int i = 0; i < nfiles; i++)
+ {
+ char *nativePath = pstrdup(paths[i]);
+ make_native_path(nativePath);
+
+ if (i > 0)
+ appendStringInfoChar(&pathbuf, ' ');
+ appendStringInfoString(&pathbuf, nativePath);
+
+ pfree(nativePath);
+ }
+
+ snprintf(nfiles_str, sizeof(nfiles_str), "%d", nfiles);
+
+ xlogarchcmd = replace_percent_placeholders(XLogArchiveCommand,
+ "archive_command", "FPN",
+ filebuf.data, pathbuf.data, nfiles_str);
+
+ ereport(DEBUG3,
+ (errmsg_internal("executing multi-file archive command: %s", xlogarchcmd)));
+
+ success = run_archive_command(xlogarchcmd, filebuf.data, nfiles);
+
+ if (success)
+ {
+ for (int i = 0; i < nfiles; i++)
+ elog(DEBUG1, "archived write-ahead log file \"%s\"", files[i]);
+ }
+
+ pfree(xlogarchcmd);
+ pfree(filebuf.data);
+ pfree(pathbuf.data);
+
+ return success;
+}
+
static void
shell_archive_shutdown(ArchiveModuleState *state)
{
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 78e39e5f866..93c3677e25f 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -93,6 +93,7 @@ typedef struct PgArchData
} PgArchData;
char *XLogArchiveLibrary = "";
+bool XLogArchiveMulti = false;
char *arch_module_check_errdetail_string;
@@ -144,6 +145,7 @@ static volatile sig_atomic_t ready_to_stop = false;
static void pgarch_waken_stop(SIGNAL_ARGS);
static void pgarch_MainLoop(void);
static void pgarch_ArchiverCopyLoop(void);
+static void pgarch_ArchiverCopyLoopMulti(void);
static bool pgarch_archiveXlog(char *xlog);
static bool pgarch_readyXlog(char *xlog);
static void pgarch_archiveDone(char *xlog);
@@ -346,7 +348,10 @@ pgarch_MainLoop(void)
}
/* Do what we're here for */
- pgarch_ArchiverCopyLoop();
+ if (XLogArchiveMulti)
+ pgarch_ArchiverCopyLoopMulti();
+ else
+ pgarch_ArchiverCopyLoop();
/*
* Sleep until a signal is received, or until a poll is forced by
@@ -372,6 +377,167 @@ pgarch_MainLoop(void)
} while (!time_to_stop);
}
+typedef bool (*ArchiveCallbackFn)(void *arg);
+
+static bool
+pgarch_safe_callback(ArchiveCallbackFn fn, void *arg)
+{
+ sigjmp_buf local_sigjmp_buf;
+ MemoryContext oldcontext;
+ bool ret;
+
+ oldcontext = MemoryContextSwitchTo(archive_context);
+
+ if (sigsetjmp(local_sigjmp_buf, 1) != 0)
+ {
+ error_context_stack = NULL;
+ HOLD_INTERRUPTS();
+ EmitErrorReport();
+ disable_all_timeouts(false);
+ LWLockReleaseAll();
+ ConditionVariableCancelSleep();
+ pgstat_report_wait_end();
+ ReleaseAuxProcessResources(false);
+ AtEOXact_Files(false);
+ AtEOXact_HashTables(false);
+
+ MemoryContextSwitchTo(oldcontext);
+ FlushErrorState();
+ MemoryContextReset(archive_context);
+
+ PG_exception_stack = NULL;
+ RESUME_INTERRUPTS();
+
+ ret = false;
+ }
+ else
+ {
+ PG_exception_stack = &local_sigjmp_buf;
+ ret = fn(arg);
+ PG_exception_stack = NULL;
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextReset(archive_context);
+ }
+
+ return ret;
+}
+
+typedef struct {
+ char **filenames;
+ char **paths;
+ int n;
+} ArchiveFilesArg;
+
+static bool
+archive_files_wrapper(void *arg)
+{
+ ArchiveFilesArg *a = (ArchiveFilesArg *) arg;
+ return ArchiveCallbacks->archive_files_cb(archive_module_state,
+ a->filenames,
+ a->paths,
+ a->n);
+}
+
+/*
+ * pgarch_ArchiverCopyLoopMulti
+ *
+ * Archives multiple outstanding WAL files in one batch using archive_files_cb.
+ */
+static void
+pgarch_ArchiverCopyLoopMulti(void)
+{
+ char *filenames[NUM_FILES_PER_DIRECTORY_SCAN];
+ char *paths[NUM_FILES_PER_DIRECTORY_SCAN];
+ int n;
+ bool ret;
+ ArchiveFilesArg arg;
+
+
+ elog(DEBUG1, "Starting archiver copy(multi)");
+
+ Assert(XLogArchiveMulti);
+ Assert(ArchiveCallbacks->archive_files_cb != NULL);
+
+ while (1)
+ {
+ PgArchForceDirScan();
+ pgarch_readyXlog(NULL);
+ n = 0;
+
+ for (int i = 0; i < arch_files->arch_files_size; i++)
+ {
+ char *arch_file = arch_files->arch_files[i];
+ char xlogpath[MAXPGPATH];
+ struct stat stat_buf;
+
+ snprintf(xlogpath, MAXPGPATH, XLOGDIR "/%s", arch_file);
+
+ if (stat(xlogpath, &stat_buf) != 0 && errno == ENOENT)
+ {
+ char xlogready[MAXPGPATH];
+
+ StatusFilePath(xlogready, arch_file, ".ready");
+ if (unlink(xlogready) == 0)
+ {
+ ereport(WARNING,
+ (errmsg("removed orphan archive status file \"%s\"", xlogready)));
+ }
+ else
+ {
+ ereport(WARNING,
+ (errmsg("could not remove orphan archive status file \"%s\": %m", xlogready)));
+ }
+ continue;
+ }
+
+ filenames[n] = arch_file;
+ paths[n] = psprintf(XLOGDIR "/%s", arch_file);
+ n++;
+ }
+
+ if (n == 0)
+ return;
+
+ if (ShutdownRequestPending || !PostmasterIsAlive())
+ return;
+
+ ProcessPgArchInterrupts();
+
+ if (ArchiveCallbacks->check_configured_cb != NULL &&
+ !ArchiveCallbacks->check_configured_cb(archive_module_state))
+ {
+ ereport(WARNING,
+ (errmsg("\"archive_mode\" enabled, yet archiving is not configured"),
+ arch_module_check_errdetail_string ?
+ errdetail_internal("%s", arch_module_check_errdetail_string) : 0));
+ return;
+ }
+
+ arg.filenames = filenames;
+ arg.paths = paths;
+ arg.n = n;
+
+ ret = pgarch_safe_callback(archive_files_wrapper, &arg);
+
+ if (!ret)
+ {
+ for (int i = 0; i < n; i++)
+ pgstat_report_archiver(filenames[i], 0);
+
+ ereport(WARNING,
+ (errmsg("archiving multiple WAL files failed")));
+ return;
+ }
+
+ for (int i = 0; i < n; i++)
+ {
+ pgarch_archiveDone(filenames[i]);
+ pgstat_report_archiver(filenames[i], 1);
+ }
+ }
+}
+
/*
* pgarch_ArchiverCopyLoop
*
@@ -506,9 +672,19 @@ pgarch_ArchiverCopyLoop(void)
}
}
+typedef struct {
+ const char *xlog;
+ const char *pathname;
+} ArchiveXlogArg;
+
+static bool
+archive_file_wrapper(void *arg)
+{
+ ArchiveXlogArg *a = (ArchiveXlogArg *) arg;
+ return ArchiveCallbacks->archive_file_cb(archive_module_state, a->xlog, a->pathname);
+}
+
/*
- * pgarch_archiveXlog
- *
* Invokes archive_file_cb to copy one archive file to wherever it should go
*
* Returns true if successful
@@ -516,104 +692,22 @@ pgarch_ArchiverCopyLoop(void)
static bool
pgarch_archiveXlog(char *xlog)
{
- sigjmp_buf local_sigjmp_buf;
- MemoryContext oldcontext;
- char pathname[MAXPGPATH];
- char activitymsg[MAXFNAMELEN + 16];
- bool ret;
+ char pathname[MAXPGPATH];
+ char activitymsg[MAXFNAMELEN + 16];
+ ArchiveXlogArg arg;
+ bool ret;
snprintf(pathname, MAXPGPATH, XLOGDIR "/%s", xlog);
-
- /* Report archive activity in PS display */
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
set_ps_display(activitymsg);
- oldcontext = MemoryContextSwitchTo(archive_context);
+ arg.xlog = xlog;
+ arg.pathname = pathname;
- /*
- * Since the archiver operates at the bottom of the exception stack,
- * ERRORs turn into FATALs and cause the archiver process to restart.
- * However, using ereport(ERROR, ...) when there are problems is easy to
- * code and maintain. Therefore, we create our own exception handler to
- * catch ERRORs and return false instead of restarting the archiver
- * whenever there is a failure.
- *
- * We assume ERRORs from the archiving callback are the most common
- * exceptions experienced by the archiver, so we opt to handle exceptions
- * here instead of PgArchiverMain() to avoid reinitializing the archiver
- * too frequently. We could instead add a sigsetjmp() block to
- * PgArchiverMain() and use PG_TRY/PG_CATCH here, but the extra code to
- * avoid the odd archiver restart doesn't seem worth it.
- */
- if (sigsetjmp(local_sigjmp_buf, 1) != 0)
- {
- /* Since not using PG_TRY, must reset error stack by hand */
- error_context_stack = NULL;
+ ret = pgarch_safe_callback(archive_file_wrapper, &arg);
- /* Prevent interrupts while cleaning up */
- HOLD_INTERRUPTS();
-
- /* Report the error to the server log. */
- EmitErrorReport();
-
- /*
- * Try to clean up anything the archive module left behind. We try to
- * cover anything that an archive module could conceivably have left
- * behind, but it is of course possible that modules could be doing
- * unexpected things that require additional cleanup. Module authors
- * should be sure to do any extra required cleanup in a PG_CATCH block
- * within the archiving callback, and they are encouraged to notify
- * the pgsql-hackers mailing list so that we can add it here.
- */
- disable_all_timeouts(false);
- LWLockReleaseAll();
- ConditionVariableCancelSleep();
- pgstat_report_wait_end();
- pgaio_error_cleanup();
- ReleaseAuxProcessResources(false);
- AtEOXact_Files(false);
- AtEOXact_HashTables(false);
-
- /*
- * Return to the original memory context and clear ErrorContext for
- * next time.
- */
- MemoryContextSwitchTo(oldcontext);
- FlushErrorState();
-
- /* Flush any leaked data */
- MemoryContextReset(archive_context);
-
- /* Remove our exception handler */
- PG_exception_stack = NULL;
-
- /* Now we can allow interrupts again */
- RESUME_INTERRUPTS();
-
- /* Report failure so that the archiver retries this file */
- ret = false;
- }
- else
- {
- /* Enable our exception handler */
- PG_exception_stack = &local_sigjmp_buf;
-
- /* Archive the file! */
- ret = ArchiveCallbacks->archive_file_cb(archive_module_state,
- xlog, pathname);
-
- /* Remove our exception handler */
- PG_exception_stack = NULL;
-
- /* Reset our memory context and switch back to the original one */
- MemoryContextSwitchTo(oldcontext);
- MemoryContextReset(archive_context);
- }
-
- if (ret)
- snprintf(activitymsg, sizeof(activitymsg), "last was %s", xlog);
- else
- snprintf(activitymsg, sizeof(activitymsg), "failed on %s", xlog);
+ snprintf(activitymsg, sizeof(activitymsg),
+ ret ? "last was %s" : "failed on %s", xlog);
set_ps_display(activitymsg);
return ret;
@@ -673,7 +767,10 @@ pgarch_readyXlog(char *xlog)
if (stat(status_file, &st) == 0)
{
- strcpy(xlog, arch_file);
+ if (xlog)
+ strcpy(xlog, arch_file);
+ else
+ arch_files->arch_files_size++;
return true;
}
else if (errno != ENOENT)
@@ -763,8 +860,11 @@ pgarch_readyXlog(char *xlog)
arch_files->arch_files[i] = DatumGetCString(binaryheap_remove_first(arch_files->arch_heap));
/* Return the highest priority file. */
- arch_files->arch_files_size--;
- strcpy(xlog, arch_files->arch_files[arch_files->arch_files_size]);
+ if (xlog)
+ {
+ arch_files->arch_files_size--;
+ strcpy(xlog, arch_files->arch_files[arch_files->arch_files_size]);
+ }
return true;
}
@@ -937,6 +1037,12 @@ LoadArchiveLibrary(void)
ArchiveCallbacks = (*archive_init) ();
+ if (XLogArchiveMulti && !ArchiveCallbacks->archive_files_cb)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("archive module does not support multi-file archiving"),
+ errdetail("The parameter \"archive_multi\" is enabled, but the archive module does not define archive_files_cb.")));
+
if (ArchiveCallbacks->archive_file_cb == NULL)
ereport(ERROR,
(errmsg("archive modules must register an archive callback")));
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index d14b1678e7f..9e6630a4145 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -799,6 +799,15 @@ struct config_bool ConfigureNamesBool[] =
true,
NULL, NULL, NULL
},
+ {
+ {"archive_multi", PGC_SIGHUP, WAL_ARCHIVING,
+ gettext_noop("Enables support for archiving multiple WAL files at once."),
+ NULL
+ },
+ &XLogArchiveMulti,
+ false,
+ NULL, NULL, NULL
+ },
{
{"enable_indexscan", PGC_USERSET, QUERY_TUNING_METHOD,
gettext_noop("Enables the planner's use of index-scan plans."),
diff --git a/src/include/archive/archive_module.h b/src/include/archive/archive_module.h
index 3b17ca7eeca..b31282ba67f 100644
--- a/src/include/archive/archive_module.h
+++ b/src/include/archive/archive_module.h
@@ -16,6 +16,7 @@
* The value of the archive_library GUC.
*/
extern PGDLLIMPORT char *XLogArchiveLibrary;
+extern PGDLLIMPORT bool XLogArchiveMulti;
typedef struct ArchiveModuleState
{
@@ -40,11 +41,28 @@ typedef bool (*ArchiveCheckConfiguredCB) (ArchiveModuleState *state);
typedef bool (*ArchiveFileCB) (ArchiveModuleState *state, const char *file, const char *path);
typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
+/*
+ * Optional callback for multi-file archiving.
+ *
+ * Called when multiple WAL segments should be archived at once.
+ * The `files` and `paths` arrays must have `n_files` elements each.
+ *
+ * Return true if all files were archived successfully.
+ */
+typedef bool (*ArchiveFilesCB) (ArchiveModuleState *state,
+ char **files,
+ char **paths,
+ int n_files);
+
+typedef void (*ArchiveShutdownCB) (ArchiveModuleState *state);
+
typedef struct ArchiveModuleCallbacks
{
ArchiveStartupCB startup_cb;
ArchiveCheckConfiguredCB check_configured_cb;
ArchiveFileCB archive_file_cb;
+ ArchiveFilesCB archive_files_cb; /* optional */
ArchiveShutdownCB shutdown_cb;
} ArchiveModuleCallbacks;
diff --git a/src/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 61f68e0cc2e..ac24bb296b4 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -637,6 +637,7 @@ sub init
$params{allows_streaming} = 0 unless defined $params{allows_streaming};
$params{force_initdb} = 0 unless defined $params{force_initdb};
$params{has_archiving} = 0 unless defined $params{has_archiving};
+ $params{is_multi} = 0 unless defined $params{is_multi};
my $initdb_extra_opts_env = $ENV{PG_TEST_INITDB_EXTRA_OPTS};
if (defined $initdb_extra_opts_env)
@@ -766,7 +767,7 @@ sub init
or die("unable to set permissions for $pgdata/postgresql.conf");
$self->set_replication_conf if $params{allows_streaming};
- $self->enable_archiving if $params{has_archiving};
+ $self->enable_archiving(is_multi => $params{is_multi}) if $params{has_archiving};
return;
}
@@ -1481,30 +1482,48 @@ sub set_standby_mode
# Internal routine to enable archiving
sub enable_archiving
{
- my ($self) = @_;
+ my ($self, %opts) = @_;
+ my $is_multi = $opts{is_multi} // 0;
my $path = $self->archive_dir;
my $name = $self->name;
print "### Enabling WAL archiving for node \"$name\"\n";
- # On Windows, the path specified in the restore command needs to use
- # double back-slashes to work properly and to be able to detect properly
- # the file targeted by the copy command, so the directory value used
- # in this routine, using only one back-slash, need to be properly changed
- # first. Paths also need to be double-quoted to prevent failures where
- # the path contains spaces.
- $path =~ s{\\}{\\\\}g if ($PostgreSQL::Test::Utils::windows_os);
- my $copy_command =
- $PostgreSQL::Test::Utils::windows_os
- ? qq{copy "%p" "$path\\\\%f"}
- : qq{cp "%p" "$path/%f"};
+ # On Windows, adjust slashes and quoting
+ if ($PostgreSQL::Test::Utils::windows_os)
+ {
+ $path =~ s{\\}{\\\\}g;
+ }
- # Enable archive_mode and archive_command on node
+ my ($copy_command);
+
+ if ($PostgreSQL::Test::Utils::windows_os)
+ {
+ if ($is_multi) {
+ $copy_command = qq{copy %P "$path"};
+ } else {
+ $copy_command = qq{copy %p "$path\\\\%f"};
+ }
+ }
+ else
+ {
+ if ($is_multi) {
+ $copy_command = qq{cp %P "$path"};
+ } else {
+ $copy_command = qq{cp %p "$path/%f"};
+ }
+ }
+
+ my $archive_multi_conf = $is_multi ? "archive_multi = true\n" : "";
+
+ # Enable archive_mode, archive_command, and optionally archive_multi on node
$self->append_conf(
'postgresql.conf', qq(
archive_mode = on
archive_command = '$copy_command'
+$archive_multi_conf
));
+
return;
}
diff --git a/src/test/recovery/t/002_archiving.pl b/src/test/recovery/t/002_archiving.pl
index 3acdb9ff1eb..a99e422f193 100644
--- a/src/test/recovery/t/002_archiving.pl
+++ b/src/test/recovery/t/002_archiving.pl
@@ -13,6 +13,7 @@ use File::Copy;
my $node_primary = PostgreSQL::Test::Cluster->new('primary');
$node_primary->init(
has_archiving => 1,
+ is_multi => 1,
allows_streaming => 1);
my $backup_name = 'my_backup';
--
2.48.1