mirror of
https://github.com/postgres/postgres.git
synced 2025-07-30 11:03:19 +03:00
Add built-in ERROR handling for archive callbacks.
Presently, the archiver process restarts when an archive callback ERRORs. To avoid this, archive module authors can use sigsetjmp(), manage a memory context, etc., but that requires a lot of extra code that will likely look roughly the same between modules. This commit adds basic archive callback ERROR handling to pgarch.c so that module authors won't ordinarily need to worry about this. While this built-in handler attempts to clean up anything that an archive module could conceivably have left behind, it is possible that some modules are 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. The archiving callback is now called in a short-lived memory context that the archiver process resets between invocations. If a module requires longer-lived storage, it must maintain its own memory context. Thanks to these changes, the basic_archive module can be greatly simplified. Suggested-by: Andres Freund Reviewed-by: Andres Freund, Yong Li Discussion: https://postgr.es/m/20230217215624.GA3131134%40nathanxps13
This commit is contained in:
@ -71,9 +71,6 @@ shell_archive_file(ArchiveModuleState *state, const char *file,
|
||||
"archive_command", "fp",
|
||||
file, nativePath);
|
||||
|
||||
if (nativePath)
|
||||
pfree(nativePath);
|
||||
|
||||
ereport(DEBUG3,
|
||||
(errmsg_internal("executing archive command \"%s\"",
|
||||
xlogarchcmd)));
|
||||
@ -132,8 +129,6 @@ shell_archive_file(ArchiveModuleState *state, const char *file,
|
||||
return false;
|
||||
}
|
||||
|
||||
pfree(xlogarchcmd);
|
||||
|
||||
elog(DEBUG1, "archived write-ahead log file \"%s\"", file);
|
||||
return true;
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "postmaster/auxprocess.h"
|
||||
#include "postmaster/interrupt.h"
|
||||
#include "postmaster/pgarch.h"
|
||||
#include "storage/condition_variable.h"
|
||||
#include "storage/fd.h"
|
||||
#include "storage/ipc.h"
|
||||
#include "storage/latch.h"
|
||||
@ -49,6 +50,8 @@
|
||||
#include "utils/guc.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "utils/ps_status.h"
|
||||
#include "utils/resowner.h"
|
||||
#include "utils/timeout.h"
|
||||
|
||||
|
||||
/* ----------
|
||||
@ -100,6 +103,7 @@ static time_t last_sigterm_time = 0;
|
||||
static PgArchData *PgArch = NULL;
|
||||
static const ArchiveModuleCallbacks *ArchiveCallbacks;
|
||||
static ArchiveModuleState *archive_module_state;
|
||||
static MemoryContext archive_context;
|
||||
|
||||
|
||||
/*
|
||||
@ -257,6 +261,11 @@ PgArchiverMain(char *startup_data, size_t startup_data_len)
|
||||
ready_file_comparator, false,
|
||||
NULL);
|
||||
|
||||
/* Initialize our memory context. */
|
||||
archive_context = AllocSetContextCreate(TopMemoryContext,
|
||||
"archiver",
|
||||
ALLOCSET_DEFAULT_SIZES);
|
||||
|
||||
/* Load the archive_library. */
|
||||
LoadArchiveLibrary();
|
||||
|
||||
@ -507,6 +516,8 @@ 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;
|
||||
@ -517,7 +528,87 @@ pgarch_archiveXlog(char *xlog)
|
||||
snprintf(activitymsg, sizeof(activitymsg), "archiving %s", xlog);
|
||||
set_ps_display(activitymsg);
|
||||
|
||||
ret = ArchiveCallbacks->archive_file_cb(archive_module_state, xlog, pathname);
|
||||
oldcontext = MemoryContextSwitchTo(archive_context);
|
||||
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/* 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();
|
||||
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
|
||||
|
Reference in New Issue
Block a user