agora inbox for pgsql-hackers@postgresql.orghelp / color / mirror / Atom feed
[PATCH v18 1/4] Introduce custodian. 24+ messages / 2 participants [nested] [flat]
* [PATCH v18 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- doc/src/sgml/glossary.sgml | 11 + src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 377 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 14 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml index 7c01a541fe..ad3f53e2a3 100644 --- a/doc/src/sgml/glossary.sgml +++ b/doc/src/sgml/glossary.sgml @@ -144,6 +144,7 @@ (but not the autovacuum workers), the <glossterm linkend="glossary-background-writer">background writer</glossterm>, the <glossterm linkend="glossary-checkpointer">checkpointer</glossterm>, + the <glossterm linkend="glossary-custodian">custodian</glossterm>, the <glossterm linkend="glossary-logger">logger</glossterm>, the <glossterm linkend="glossary-startup-process">startup process</glossterm>, the <glossterm linkend="glossary-wal-archiver">WAL archiver</glossterm>, @@ -484,6 +485,16 @@ </glossdef> </glossentry> + <glossentry id="glossary-custodian"> + <glossterm>Custodian (process)</glossterm> + <glossdef> + <para> + An <glossterm linkend="glossary-auxiliary-proc">auxiliary process</glossterm> + that is responsible for executing assorted cleanup tasks. + </para> + </glossdef> + </glossentry> + <glossentry> <glossterm>Data area</glossterm> <glosssee otherterm="glossary-data-directory" /> diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e5af958999 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,377 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + ReleaseAuxProcessResources(false); + AtEOXact_Files(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise our latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --LZvS9be/3tNcYl/X Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v18-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v13 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index c83cc8cc6c..00d18ee761 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --CE+1k2dSO48ffgeK Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v13-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v17 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --ew6BAiZeqk4r7MaW Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v17-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v17 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --ew6BAiZeqk4r7MaW Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v17-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v11 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 489 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 383bc4776e..b1b249cc90 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -248,6 +248,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -544,6 +545,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1821,13 +1823,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2746,6 +2751,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3066,6 +3073,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3159,6 +3168,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3616,6 +3639,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3793,6 +3828,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3830,6 +3868,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3919,6 +3958,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4113,6 +4153,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 683f616b1a..0131862973 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index ee48e392ed..c2e9bb3a75 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -426,6 +427,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -438,6 +440,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 91824b4691..86acd3a5b9 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -396,6 +396,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -413,11 +415,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --envbJBWh7q8WU6mo Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v11-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v6 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 252 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 20 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 12 files changed, 345 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 39ac4490db..620a0b1bae 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..db00282658 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,252 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). Also, ensure that any offloaded + * tasks are either not required during single-user mode or are performed + * separately during single-user mode. + * + * The custodian is not an essential process and can shutdown quickly when + * requested. The custodian will wake up approximately once every 5 + * minutes to perform its tasks, but backends can (and should) set its + * latch to wake it up sooner. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <time.h> + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +#define CUSTODIAN_TIMEOUT_S (300) /* 5 minutes */ + +typedef struct +{ + slock_t cust_lck; + int cust_flags; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * On startup and after an exception, we won't know exactly what tasks need + * to be performed, so request all of them. + */ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->cust_flags = 0xFFFFFFFF; + SpinLockRelease(&CustodianShmem->cust_lck); + + /* + * Loop forever + */ + for (;;) + { + pg_time_t start_time; + pg_time_t end_time; + int elapsed_secs; + int cur_timeout; + int flags; + + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + start_time = (pg_time_t) time(NULL); + + /* Obtain requested tasks */ + SpinLockAcquire(&CustodianShmem->cust_lck); + flags = CustodianShmem->cust_flags; + CustodianShmem->cust_flags = 0; + SpinLockRelease(&CustodianShmem->cust_lck); + + /* TODO: offloaded tasks go here */ + + /* Calculate how long to sleep */ + end_time = (pg_time_t) time(NULL); + elapsed_secs = end_time - start_time; + if (elapsed_secs >= CUSTODIAN_TIMEOUT_S) + continue; /* no sleep for us */ + cur_timeout = CUSTODIAN_TIMEOUT_S - elapsed_secs; + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + cur_timeout * 1000L /* convert to ms */ , + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + */ +void +RequestCustodian(int flags) +{ + SpinLockAcquire(&CustodianShmem->cust_lck); + CustodianShmem->cust_flags |= flags; + SpinLockRelease(&CustodianShmem->cust_lck); + + if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index dde4bc25b1..5162ee9dec 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -251,6 +251,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -548,6 +549,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1823,13 +1825,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2769,6 +2774,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3089,6 +3096,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3182,6 +3191,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3639,6 +3662,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3816,6 +3851,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3853,6 +3891,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3942,6 +3981,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4135,6 +4175,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 1a6f527051..b19d743cab 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -129,6 +130,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -277,6 +279,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 87c15b9c6f..469768c4e4 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index b25bd0e583..66bf42e5b1 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -273,6 +273,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_STARTUP: backendDesc = "startup"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 0af130fbc5..ffe9404c68 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -330,6 +330,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, @@ -433,6 +434,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -445,6 +447,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..c95a7c7de6 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,20 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(int flags); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619eb..467421e371 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -394,6 +394,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -411,11 +413,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index b578e2ec75..7524e197e5 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --liOOAslEiF7prFVr Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v6-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v5 1/8] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 214 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++++- src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 17 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 11 files changed, 301 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index dbbeac5a82..1b7aae60f5 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 0587e45920..7eae34884d 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..5f2b647544 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,214 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process is new as of Postgres 15. It's main purpose is to + * offload tasks that could otherwise delay startup and checkpointing, but + * it needn't be restricted to just those things. Offloaded tasks should + * not be synchronous (e.g., checkpointing shouldn't need to wait for the + * custodian to complete a task before proceeding). Also, ensure that any + * offloaded tasks are either not required during single-user mode or are + * performed separately during single-user mode. + * + * The custodian is not an essential process and can shutdown quickly when + * requested. The custodian will wake up approximately once every 5 + * minutes to perform its tasks, but backends can (and should) set its + * latch to wake it up sooner. + * + * Normal termination is by SIGTERM, which instructs the bgwriter to + * exit(0). Emergency termination is by SIGQUIT; like any backend, the + * custodian will simply abort and exit on SIGQUIT. + * + * If the custodian exits unexpectedly, the postmaster treats that the same + * as a backend crash: shared memory may be corrupted, so remaining + * backends should be killed by SIGQUIT and then a recovery cycle started. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <time.h> + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "utils/memutils.h" + +#define CUSTODIAN_TIMEOUT_S (300) /* 5 minutes */ + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. + * + * You might wonder why this isn't coded as an infinite loop around a + * PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception handler + * in force at all during the CATCH part. By leaving the outermost setjmp + * always active, we have at least some chance of recovering from an error + * during error recovery. (If we get into an infinite loop thereby, it + * will soon be stopped by overflow of elog.c's internal state stack.) + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPS() + * call redundant, but it is not since InterruptPending might be set + * already. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + pgstat_report_wait_end(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + pg_time_t start_time; + pg_time_t end_time; + int elapsed_secs; + int cur_timeout; + + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + start_time = (pg_time_t) time(NULL); + + /* TODO: offloaded tasks go here */ + + /* Calculate how long to sleep */ + end_time = (pg_time_t) time(NULL); + elapsed_secs = end_time - start_time; + if (elapsed_secs >= CUSTODIAN_TIMEOUT_S) + continue; /* no sleep for us */ + cur_timeout = CUSTODIAN_TIMEOUT_S - elapsed_secs; + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + cur_timeout * 1000L /* convert to ms */ , + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 735fed490b..a867412268 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -251,6 +251,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -557,6 +558,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1818,13 +1820,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2781,6 +2786,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3108,6 +3115,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3210,6 +3219,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3683,6 +3706,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3886,6 +3921,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3923,6 +3961,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -4016,6 +4055,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4221,6 +4261,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 90283f8a9f..1e693b69e5 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -181,6 +181,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 60972c3a75..e10cc2d82b 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 0868e5a24f..8b52757ea6 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -274,6 +274,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_STARTUP: backendDesc = "startup"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 0abc3ad540..71f522878e 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -328,6 +328,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, @@ -432,6 +433,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -444,6 +446,7 @@ extern AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..cf0a04ca6c --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +extern void CustodianMain(void) pg_attribute_noreturn(); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index a58888f9e9..ad61b4d802 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -357,6 +357,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -374,11 +376,12 @@ extern PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 395d325c5f..1338d06823 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_PGSTAT_MAIN, -- 2.25.1 --2oS5YaxWCcQjTEyO Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v5-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v19 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- doc/src/sgml/glossary.sgml | 11 + src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 377 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 14 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml index 7c01a541fe..ad3f53e2a3 100644 --- a/doc/src/sgml/glossary.sgml +++ b/doc/src/sgml/glossary.sgml @@ -144,6 +144,7 @@ (but not the autovacuum workers), the <glossterm linkend="glossary-background-writer">background writer</glossterm>, the <glossterm linkend="glossary-checkpointer">checkpointer</glossterm>, + the <glossterm linkend="glossary-custodian">custodian</glossterm>, the <glossterm linkend="glossary-logger">logger</glossterm>, the <glossterm linkend="glossary-startup-process">startup process</glossterm>, the <glossterm linkend="glossary-wal-archiver">WAL archiver</glossterm>, @@ -484,6 +485,16 @@ </glossdef> </glossentry> + <glossentry id="glossary-custodian"> + <glossterm>Custodian (process)</glossterm> + <glossdef> + <para> + An <glossterm linkend="glossary-auxiliary-proc">auxiliary process</glossterm> + that is responsible for executing assorted cleanup tasks. + </para> + </glossdef> + </glossentry> + <glossentry> <glossterm>Data area</glossterm> <glosssee otherterm="glossary-data-directory" /> diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index cae6feb356..a1f042f13a 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..98bb9efcfd --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,377 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + ReleaseAuxProcessResources(false); + AtEOXact_Files(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + sigprocmask(SIG_SETMASK, &UnBlockSig, NULL); + + /* + * Advertise our latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 9079922de7..63f2abe3a1 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -6,6 +6,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2552327d90..e3aef4081e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -249,6 +249,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -560,6 +561,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1795,13 +1797,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2732,6 +2737,8 @@ process_pm_reload_request(void) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3085,6 +3092,8 @@ process_pm_child_exit(void) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3178,6 +3187,20 @@ process_pm_child_exit(void) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3590,6 +3613,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3744,6 +3773,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3781,6 +3813,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3877,6 +3910,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4092,6 +4126,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 8f1ded7338..4268a941ad 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 22b4278610..40a83636fe 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -178,6 +178,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 6e4599278c..9348b441ba 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 59532bbd80..25c2ba97b8 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -283,6 +283,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 96b3a1e1a0..738e7f8fb0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -324,6 +324,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -430,6 +431,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -442,6 +444,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 4258cd92c9..25e00a14ff 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6cacd6edaf..ea8ba623e9 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN, -- 2.25.1 --x+6KMIRAuhnl3hBn Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v19-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v20 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- doc/src/sgml/glossary.sgml | 11 + src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 377 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/pgstat_io.c | 4 +- src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 15 files changed, 491 insertions(+), 6 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/doc/src/sgml/glossary.sgml b/doc/src/sgml/glossary.sgml index 7c01a541fe..ad3f53e2a3 100644 --- a/doc/src/sgml/glossary.sgml +++ b/doc/src/sgml/glossary.sgml @@ -144,6 +144,7 @@ (but not the autovacuum workers), the <glossterm linkend="glossary-background-writer">background writer</glossterm>, the <glossterm linkend="glossary-checkpointer">checkpointer</glossterm>, + the <glossterm linkend="glossary-custodian">custodian</glossterm>, the <glossterm linkend="glossary-logger">logger</glossterm>, the <glossterm linkend="glossary-startup-process">startup process</glossterm>, the <glossterm linkend="glossary-wal-archiver">WAL archiver</glossterm>, @@ -484,6 +485,16 @@ </glossdef> </glossentry> + <glossentry id="glossary-custodian"> + <glossterm>Custodian (process)</glossterm> + <glossdef> + <para> + An <glossterm linkend="glossary-auxiliary-proc">auxiliary process</glossterm> + that is responsible for executing assorted cleanup tasks. + </para> + </glossdef> + </glossentry> + <glossentry> <glossterm>Data area</glossterm> <glosssee otherterm="glossary-data-directory" /> diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 047448b34e..5f4dde85cf 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index cae6feb356..a1f042f13a 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..98bb9efcfd --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,377 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + ReleaseAuxProcessResources(false); + AtEOXact_Files(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + sigprocmask(SIG_SETMASK, &UnBlockSig, NULL); + + /* + * Advertise our latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index cda921fd10..faaaba6a21 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -6,6 +6,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 2552327d90..e3aef4081e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -249,6 +249,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -560,6 +561,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1795,13 +1797,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2732,6 +2737,8 @@ process_pm_reload_request(void) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3085,6 +3092,8 @@ process_pm_child_exit(void) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3178,6 +3187,20 @@ process_pm_child_exit(void) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3590,6 +3613,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3744,6 +3773,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3781,6 +3813,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3877,6 +3910,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4092,6 +4126,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 8f1ded7338..4268a941ad 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 22b4278610..40a83636fe 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -178,6 +178,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/pgstat_io.c b/src/backend/utils/activity/pgstat_io.c index 0e07e0848d..f05590f2c4 100644 --- a/src/backend/utils/activity/pgstat_io.c +++ b/src/backend/utils/activity/pgstat_io.c @@ -224,7 +224,8 @@ pgstat_io_snapshot_cb(void) * - Syslogger because it is not connected to shared memory * - Archiver because most relevant archiving IO is delegated to a * specialized command or module -* - WAL Receiver and WAL Writer IO is not tracked in pg_stat_io for now +* - WAL Receiver, WAL Writer, and Custodian IO is not tracked in pg_stat_io for +* now * * Function returns true if BackendType participates in the cumulative stats * subsystem for IO and false if it does not. @@ -243,6 +244,7 @@ pgstat_tracks_io_bktype(BackendType bktype) { case B_INVALID: case B_ARCHIVER: + case B_CUSTODIAN: case B_LOGGER: case B_WAL_RECEIVER: case B_WAL_WRITER: diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index cb99cc6339..6ca751dd1f 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 59532bbd80..25c2ba97b8 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -283,6 +283,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index c309e0233d..50f407a0bf 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -324,6 +324,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -432,6 +433,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -444,6 +446,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 4258cd92c9..25e00a14ff 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 9ab23e1c4a..a100dbca3b 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN, -- 2.25.1 --CE+1k2dSO48ffgeK Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v20-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v12 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 489 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 0b637ba6a2..3706eec25e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -248,6 +248,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -544,6 +545,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1821,13 +1823,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2746,6 +2751,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3066,6 +3073,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3159,6 +3168,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3616,6 +3639,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3793,6 +3828,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3830,6 +3868,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3919,6 +3958,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4113,6 +4153,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 13fa07b0ff..1bae34d1ee 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 8d096fdeeb..448dde0161 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --UlVJffcvxoiEqYs2 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v12-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v14 1/3] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --BXVAT5kNtrzKuDFl Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v14-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v14 1/3] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --BXVAT5kNtrzKuDFl Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v14-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v15 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --HcAYCG3uE/tztfnV Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v15-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v16 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --lrZ03NoBR/3+SXJZ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v16-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v12 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 489 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 0b637ba6a2..3706eec25e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -248,6 +248,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -544,6 +545,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1821,13 +1823,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2746,6 +2751,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3066,6 +3073,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3159,6 +3168,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3616,6 +3639,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3793,6 +3828,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3830,6 +3868,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3919,6 +3958,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4113,6 +4153,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 13fa07b0ff..1bae34d1ee 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 8d096fdeeb..448dde0161 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --UlVJffcvxoiEqYs2 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v12-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v13 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 483 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index c83cc8cc6c..00d18ee761 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --CE+1k2dSO48ffgeK Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v13-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v15 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --HcAYCG3uE/tztfnV Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v15-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v16 1/4] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 382 ++++++++++++++++++++++++ src/backend/postmaster/meson.build | 1 + src/backend/postmaster/postmaster.c | 38 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 13 files changed, 482 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..a94381bc21 --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,382 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(void); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If we are not in a standalone backend, the custodian will re-enqueue the + * currently running task if an exception is encountered. + */ +static void +DoCustodianTasks(void) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (IsPostmasterEnvironment) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * In standalone backends, the task is performed immediately in the current + * process, and this function will not return until it completes. Otherwise, + * the task is added to the custodian's queue if it is not already enqueued, + * and this function returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (!IsPostmasterEnvironment) + DoCustodianTasks(); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build index 293a44ca29..ac72a8a07f 100644 --- a/src/backend/postmaster/meson.build +++ b/src/backend/postmaster/meson.build @@ -4,6 +4,7 @@ backend_sources += files( 'bgworker.c', 'bgwriter.c', 'checkpointer.c', + 'custodian.c', 'fork_process.c', 'interrupt.c', 'pgarch.c', diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index a8a246921f..6a74423172 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -240,6 +240,7 @@ bool send_abort_for_kill = false; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -537,6 +538,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1808,13 +1810,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2728,6 +2733,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3025,6 +3032,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3118,6 +3127,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3532,6 +3555,12 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) else if (CheckpointerPID != 0 && take_action) sigquit_child(CheckpointerPID); + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + sigquit_child(CustodianPID); + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3685,6 +3714,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3722,6 +3754,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3815,6 +3848,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4027,6 +4061,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index b204ecdbc3..cf80e65779 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -130,6 +131,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -278,6 +280,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index b1c35653fc..6a8485e865 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index b2abd75ddb..63fd242b1e 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb1046450b..f19f4c3075 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 795182fa51..59a95dd7c0 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -429,6 +430,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -441,6 +443,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..73d0bc5f02 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index aa13e1d66e..8f0e696663 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -400,6 +400,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -417,11 +419,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 0b2100be4a..48602c8a16 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --lrZ03NoBR/3+SXJZ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v16-0002-Move-removal-of-old-serialized-snapshots-to-cust.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v10 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 12 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 1664fcee2a..b25c180886 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -248,6 +248,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -544,6 +545,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1821,13 +1823,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2750,6 +2755,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3070,6 +3077,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3163,6 +3172,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3620,6 +3643,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3797,6 +3832,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3834,6 +3872,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3923,6 +3962,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4117,6 +4157,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 1a6f527051..b19d743cab 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -129,6 +130,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -277,6 +279,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 683f616b1a..0131862973 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 65cf4ba50f..36a83018e2 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -426,6 +427,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -438,6 +440,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619eb..467421e371 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -394,6 +394,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -411,11 +413,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --+QahgC5+KEYLbs62 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v10-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v8 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 12 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 81cb585891..d705ff6bf0 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -251,6 +251,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -547,6 +548,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1826,13 +1828,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2755,6 +2760,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3075,6 +3082,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3168,6 +3177,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3625,6 +3648,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3802,6 +3837,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3839,6 +3877,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3928,6 +3967,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4122,6 +4162,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 1a6f527051..b19d743cab 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -129,6 +130,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -277,6 +279,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index bd973ba613..22037f0d99 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -273,6 +273,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_STARTUP: backendDesc = "startup"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 067b729d5a..ffd59616b7 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -322,6 +322,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, @@ -425,6 +426,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -437,6 +439,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619eb..467421e371 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -394,6 +394,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -411,11 +413,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --X1bOJ3K7DJ5YkBrT Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v8-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v9 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 12 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 7765d1c83d..c275271c95 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 1664fcee2a..b25c180886 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -248,6 +248,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -544,6 +545,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1821,13 +1823,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2750,6 +2755,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3070,6 +3077,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3163,6 +3172,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3620,6 +3643,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3797,6 +3832,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3834,6 +3872,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3923,6 +3962,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4117,6 +4157,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 1a6f527051..b19d743cab 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -129,6 +130,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -277,6 +279,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 92f24a6c9b..d8e6ea45bc 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 683f616b1a..0131862973 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -278,6 +278,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_LOGGER: backendDesc = "logger"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 65cf4ba50f..36a83018e2 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -323,6 +323,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_LOGGER, B_STANDALONE_BACKEND, B_STARTUP, @@ -426,6 +427,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -438,6 +440,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619eb..467421e371 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -394,6 +394,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -411,11 +413,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 6f2d5612e0..58455dc016 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --y0ulUmNC+osPPQO6 Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v7 1/6] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 383 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++- src/backend/storage/ipc/ipci.c | 3 + src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 32 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 12 files changed, 488 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index 3a794e54d6..e1e1d1123f 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 39ac4490db..620a0b1bae 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..e90f5d0d1f --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,383 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process handles a variety of non-critical tasks that might + * otherwise delay startup, checkpointing, etc. Offloaded tasks should not + * be synchronous (e.g., checkpointing shouldn't wait for the custodian to + * complete a task before proceeding). However, tasks can be synchronously + * executed when necessary (e.g., single-user mode). The custodian is not + * an essential process and can shutdown quickly when requested. The + * custodian only wakes up to perform its tasks when its latch is set. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/fd.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "storage/smgr.h" +#include "utils/memutils.h" + +static void DoCustodianTasks(bool retry); +static CustodianTask CustodianGetNextTask(void); +static void CustodianEnqueueTask(CustodianTask task); +static const struct cust_task_funcs_entry *LookupCustodianFunctions(CustodianTask task); + +typedef struct +{ + slock_t cust_lck; + + CustodianTask task_queue_elems[NUM_CUSTODIAN_TASKS]; + int task_queue_head; +} CustodianShmemStruct; + +static CustodianShmemStruct *CustodianShmem; + +typedef void (*CustodianTaskFunction) (void); +typedef void (*CustodianTaskHandleArg) (Datum arg); + +struct cust_task_funcs_entry +{ + CustodianTask task; + CustodianTaskFunction task_func; /* performs task */ + CustodianTaskHandleArg handle_arg_func; /* handles additional info in request */ +}; + +/* + * Add new tasks here. + * + * task_func is the logic that will be executed via DoCustodianTasks() when the + * matching task is requested via RequestCustodian(). handle_arg_func is an + * optional function for providing extra information for the next invocation of + * the task. Typically, the extra information should be stored in shared + * memory for access from the custodian process. handle_arg_func is invoked + * before enqueueing the task, and it will still be invoked regardless of + * whether the task is already enqueued. + */ +static const struct cust_task_funcs_entry cust_task_functions[] = { + {INVALID_CUSTODIAN_TASK, NULL, NULL} /* must be last */ +}; + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. As with other + * auxiliary processes, we cannot use PG_TRY because this is the bottom of + * the exception stack. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + DoCustodianTasks(true); + + (void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0, + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} + +/* + * DoCustodianTasks + * Perform requested custodian tasks + * + * If retry is true, the custodian will re-enqueue the currently running task if + * an exception is encountered. + */ +static void +DoCustodianTasks(bool retry) +{ + CustodianTask task; + + while ((task = CustodianGetNextTask()) != INVALID_CUSTODIAN_TASK) + { + CustodianTaskFunction func = (LookupCustodianFunctions(task))->task_func; + + PG_TRY(); + { + (*func) (); + } + PG_CATCH(); + { + if (retry) + CustodianEnqueueTask(task); + + PG_RE_THROW(); + } + PG_END_TRY(); + } +} + +Size +CustodianShmemSize(void) +{ + return sizeof(CustodianShmemStruct); +} + +void +CustodianShmemInit(void) +{ + Size size = CustodianShmemSize(); + bool found; + + CustodianShmem = (CustodianShmemStruct *) + ShmemInitStruct("Custodian Data", size, &found); + + if (!found) + { + memset(CustodianShmem, 0, size); + SpinLockInit(&CustodianShmem->cust_lck); + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + CustodianShmem->task_queue_elems[i] = INVALID_CUSTODIAN_TASK; + } +} + +/* + * RequestCustodian + * Called to request a custodian task. + * + * If immediate is true, the task is performed immediately in the current + * process, and this function will not return until it completes. This is + * mostly useful for single-user mode. If immediate is false, the task is added + * to the custodian's queue if it is not already enqueued, and this function + * returns without waiting for the task to complete. + * + * arg can be used to provide additional information to the custodian that is + * necessary for the task. Typically, the handling function should store this + * information in shared memory for later use by the custodian. Note that the + * task's handling function for arg is invoked before enqueueing the task, and + * it will still be invoked regardless of whether the task is already enqueued. + */ +void +RequestCustodian(CustodianTask requested, bool immediate, Datum arg) +{ + CustodianTaskHandleArg arg_func = (LookupCustodianFunctions(requested))->handle_arg_func; + + /* First process any extra information provided in the request. */ + if (arg_func) + (*arg_func) (arg); + + CustodianEnqueueTask(requested); + + if (immediate) + DoCustodianTasks(false); + else if (ProcGlobal->custodianLatch) + SetLatch(ProcGlobal->custodianLatch); +} + +/* + * CustodianEnqueueTask + * Add a task to the custodian's queue + * + * If the task is already in the queue, this function has no effect. + */ +static void +CustodianEnqueueTask(CustodianTask task) +{ + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + SpinLockAcquire(&CustodianShmem->cust_lck); + + for (int i = 0; i < NUM_CUSTODIAN_TASKS; i++) + { + int idx = (CustodianShmem->task_queue_head + i) % NUM_CUSTODIAN_TASKS; + CustodianTask *elem = &CustodianShmem->task_queue_elems[idx]; + + /* + * If the task is already queued in this slot or the slot is empty, + * enqueue the task here and return. + */ + if (*elem == INVALID_CUSTODIAN_TASK || *elem == task) + { + *elem = task; + SpinLockRelease(&CustodianShmem->cust_lck); + return; + } + } + + /* We should never run out of space in the queue. */ + elog(ERROR, "could not enqueue custodian task %d", task); + pg_unreachable(); +} + +/* + * CustodianGetNextTask + * Retrieve the next task that the custodian should execute + * + * The returned task is dequeued from the custodian's queue. If no tasks are + * queued, INVALID_CUSTODIAN_TASK is returned. + */ +static CustodianTask +CustodianGetNextTask(void) +{ + CustodianTask next_task; + CustodianTask *elem; + + SpinLockAcquire(&CustodianShmem->cust_lck); + + elem = &CustodianShmem->task_queue_elems[CustodianShmem->task_queue_head]; + + next_task = *elem; + *elem = INVALID_CUSTODIAN_TASK; + + CustodianShmem->task_queue_head++; + CustodianShmem->task_queue_head %= NUM_CUSTODIAN_TASKS; + + SpinLockRelease(&CustodianShmem->cust_lck); + + return next_task; +} + +/* + * LookupCustodianFunctions + * Given a custodian task, look up its function pointers. + */ +static const struct cust_task_funcs_entry * +LookupCustodianFunctions(CustodianTask task) +{ + const struct cust_task_funcs_entry *entry; + + Assert(task >= 0 && task < NUM_CUSTODIAN_TASKS); + + for (entry = cust_task_functions; + entry && entry->task != INVALID_CUSTODIAN_TASK; + entry++) + { + if (entry->task == task) + return entry; + } + + /* All tasks must have an entry. */ + elog(ERROR, "could not lookup functions for custodian task %d", task); + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index d7257e4056..1f707d64ac 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -251,6 +251,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -548,6 +549,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1822,13 +1824,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2768,6 +2773,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3088,6 +3095,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3181,6 +3190,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3638,6 +3661,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3815,6 +3850,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3852,6 +3890,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -3941,6 +3980,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4134,6 +4174,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 1a6f527051..b19d743cab 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -30,6 +30,7 @@ #include "postmaster/autovacuum.h" #include "postmaster/bgworker_internals.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/postmaster.h" #include "replication/logicallauncher.h" #include "replication/origin.h" @@ -129,6 +130,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, PMSignalShmemSize()); size = add_size(size, ProcSignalShmemSize()); size = add_size(size, CheckpointerShmemSize()); + size = add_size(size, CustodianShmemSize()); size = add_size(size, AutoVacuumShmemSize()); size = add_size(size, ReplicationSlotsShmemSize()); size = add_size(size, ReplicationOriginShmemSize()); @@ -277,6 +279,7 @@ CreateSharedMemoryAndSemaphores(void) PMSignalShmemInit(); ProcSignalShmemInit(); CheckpointerShmemInit(); + CustodianShmemInit(); AutoVacuumShmemInit(); ReplicationSlotsShmemInit(); ReplicationOriginShmemInit(); diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37aaab1338..f297f489c9 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -180,6 +180,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 87c15b9c6f..469768c4e4 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index eb43b2c5e5..1210c2e7a3 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -273,6 +273,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_STARTUP: backendDesc = "startup"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 0af130fbc5..ffe9404c68 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -330,6 +330,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, @@ -433,6 +434,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -445,6 +447,7 @@ extern PGDLLIMPORT AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..170ca61a21 --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,32 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +/* + * If you add a new task here, be sure to add its corresponding function + * pointers to cust_task_functions in custodian.c. + */ +typedef enum CustodianTask +{ + FAKE_TASK, /* placeholder until we have a real task */ + + NUM_CUSTODIAN_TASKS, /* new tasks go above */ + INVALID_CUSTODIAN_TASK +} CustodianTask; + +extern void CustodianMain(void) pg_attribute_noreturn(); +extern Size CustodianShmemSize(void); +extern void CustodianShmemInit(void); +extern void RequestCustodian(CustodianTask task, bool immediate, Datum arg); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 2579e619eb..467421e371 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -394,6 +394,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -411,11 +413,12 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index b578e2ec75..7524e197e5 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_RECOVERY_WAL_STREAM, -- 2.25.1 --/04w6evG8XlLl3ft Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v7-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v4 1/8] Introduce custodian. @ 2022-01-05 19:24 Nathan Bossart <bossartn@amazon.com> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2022-01-05 19:24 UTC (permalink / raw) The custodian process is a new auxiliary process that is intended to help offload tasks could otherwise delay startup and checkpointing. This commit simply adds the new process; it does not yet do anything useful. --- src/backend/postmaster/Makefile | 1 + src/backend/postmaster/auxprocess.c | 8 + src/backend/postmaster/custodian.c | 213 ++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 44 ++++- src/backend/storage/lmgr/proc.c | 1 + src/backend/utils/activity/wait_event.c | 3 + src/backend/utils/init/miscinit.c | 3 + src/include/miscadmin.h | 3 + src/include/postmaster/custodian.h | 17 ++ src/include/storage/proc.h | 11 +- src/include/utils/wait_event.h | 1 + 11 files changed, 300 insertions(+), 5 deletions(-) create mode 100644 src/backend/postmaster/custodian.c create mode 100644 src/include/postmaster/custodian.h diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile index dbbeac5a82..1b7aae60f5 100644 --- a/src/backend/postmaster/Makefile +++ b/src/backend/postmaster/Makefile @@ -18,6 +18,7 @@ OBJS = \ bgworker.o \ bgwriter.o \ checkpointer.o \ + custodian.o \ fork_process.o \ interrupt.o \ pgarch.o \ diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c index 0587e45920..7eae34884d 100644 --- a/src/backend/postmaster/auxprocess.c +++ b/src/backend/postmaster/auxprocess.c @@ -20,6 +20,7 @@ #include "pgstat.h" #include "postmaster/auxprocess.h" #include "postmaster/bgwriter.h" +#include "postmaster/custodian.h" #include "postmaster/startup.h" #include "postmaster/walwriter.h" #include "replication/walreceiver.h" @@ -74,6 +75,9 @@ AuxiliaryProcessMain(AuxProcType auxtype) case CheckpointerProcess: MyBackendType = B_CHECKPOINTER; break; + case CustodianProcess: + MyBackendType = B_CUSTODIAN; + break; case WalWriterProcess: MyBackendType = B_WAL_WRITER; break; @@ -153,6 +157,10 @@ AuxiliaryProcessMain(AuxProcType auxtype) CheckpointerMain(); proc_exit(1); + case CustodianProcess: + CustodianMain(); + proc_exit(1); + case WalWriterProcess: WalWriterMain(); proc_exit(1); diff --git a/src/backend/postmaster/custodian.c b/src/backend/postmaster/custodian.c new file mode 100644 index 0000000000..dd86f0f5ce --- /dev/null +++ b/src/backend/postmaster/custodian.c @@ -0,0 +1,213 @@ +/*------------------------------------------------------------------------- + * + * custodian.c + * + * The custodian process is new as of Postgres 15. It's main purpose is to + * offload tasks that could otherwise delay startup and checkpointing, but + * it needn't be restricted to just those things. Offloaded tasks should + * not be synchronous (e.g., checkpointing shouldn't need to wait for the + * custodian to complete a task before proceeding). Also, ensure that any + * offloaded tasks are either not required during single-user mode or are + * performed separately during single-user mode. + * + * The custodian is not an essential process and can shutdown quickly when + * requested. The custodian will wake up approximately once every 5 + * minutes to perform its tasks, but backends can (and should) set its + * latch to wake it up sooner. + * + * Normal termination is by SIGTERM, which instructs the bgwriter to + * exit(0). Emergency termination is by SIGQUIT; like any backend, the + * custodian will simply abort and exit on SIGQUIT. + * + * If the custodian exits unexpectedly, the postmaster treats that the same + * as a backend crash: shared memory may be corrupted, so remaining + * backends should be killed by SIGQUIT and then a recovery cycle started. + * + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * + * IDENTIFICATION + * src/backend/postmaster/custodian.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <time.h> + +#include "libpq/pqsignal.h" +#include "pgstat.h" +#include "postmaster/custodian.h" +#include "postmaster/interrupt.h" +#include "storage/bufmgr.h" +#include "storage/condition_variable.h" +#include "storage/proc.h" +#include "storage/procsignal.h" +#include "utils/memutils.h" + +#define CUSTODIAN_TIMEOUT_S (300) /* 5 minutes */ + +/* + * Main entry point for custodian process + * + * This is invoked from AuxiliaryProcessMain, which has already created the + * basic execution environment, but not enabled signals yet. + */ +void +CustodianMain(void) +{ + sigjmp_buf local_sigjmp_buf; + MemoryContext custodian_context; + + /* + * Properly accept or ignore signals that might be sent to us. + */ + pqsignal(SIGHUP, SignalHandlerForConfigReload); + pqsignal(SIGINT, SignalHandlerForShutdownRequest); + pqsignal(SIGTERM, SignalHandlerForShutdownRequest); + /* SIGQUIT handler was already set up by InitPostmasterChild */ + pqsignal(SIGALRM, SIG_IGN); + pqsignal(SIGPIPE, SIG_IGN); + pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR2, SIG_IGN); + + /* + * Reset some signals that are accepted by postmaster but not here + */ + pqsignal(SIGCHLD, SIG_DFL); + + /* + * Create a memory context that we will do all our work in. We do this so + * that we can reset the context during error recovery and thereby avoid + * possible memory leaks. + */ + custodian_context = AllocSetContextCreate(TopMemoryContext, + "Custodian", + ALLOCSET_DEFAULT_SIZES); + MemoryContextSwitchTo(custodian_context); + + /* + * If an exception is encountered, processing resumes here. + * + * You might wonder why this isn't coded as an infinite loop around a + * PG_TRY construct. The reason is that this is the bottom of the + * exception stack, and so with PG_TRY there would be no exception handler + * in force at all during the CATCH part. By leaving the outermost setjmp + * always active, we have at least some chance of recovering from an error + * during error recovery. (If we get into an infinite loop thereby, it + * will soon be stopped by overflow of elog.c's internal state stack.) + * + * Note that we use sigsetjmp(..., 1), so that the prevailing signal mask + * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, + * signals other than SIGQUIT will be blocked until we complete error + * recovery. It might seem that this policy makes the HOLD_INTERRUPS() + * call redundant, but it is not since InterruptPending might be set + * already. + */ + 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(); + + /* + * These operations are really just a minimal subset of + * AbortTransaction(). We don't have very many resources to worry + * about. + */ + LWLockReleaseAll(); + ConditionVariableCancelSleep(); + pgstat_report_wait_end(); + AbortBufferIO(); + UnlockBuffers(); + ReleaseAuxProcessResources(false); + AtEOXact_Buffers(false); + AtEOXact_SMgr(); + AtEOXact_Files(false); + AtEOXact_HashTables(false); + + /* + * Now return to normal top-level context and clear ErrorContext for + * next time. + */ + MemoryContextSwitchTo(custodian_context); + FlushErrorState(); + + /* Flush any leaked data in the top-level context */ + MemoryContextResetAndDeleteChildren(custodian_context); + + /* Now we can allow interrupts again */ + RESUME_INTERRUPTS(); + + /* + * Sleep at least 1 second after any error. A write error is likely + * to be repeated, and we don't want to be filling the error logs as + * fast as we can. + */ + pg_usleep(1000000L); + + /* + * Close all open files after any error. This is helpful on Windows, + * where holding deleted files open causes various strange errors. + * It's not clear we need it elsewhere, but shouldn't hurt. + */ + smgrcloseall(); + + /* Report wait end here, when there is no further possibility of wait */ + pgstat_report_wait_end(); + } + + /* We can now handle ereport(ERROR) */ + PG_exception_stack = &local_sigjmp_buf; + + /* + * Unblock signals (they were blocked when the postmaster forked us) + */ + PG_SETMASK(&UnBlockSig); + + /* + * Advertise out latch that backends can use to wake us up while we're + * sleeping. + */ + ProcGlobal->custodianLatch = &MyProc->procLatch; + + /* + * Loop forever + */ + for (;;) + { + pg_time_t start_time; + pg_time_t end_time; + int elapsed_secs; + int cur_timeout; + + /* Clear any already-pending wakeups */ + ResetLatch(MyLatch); + + HandleMainLoopInterrupts(); + + start_time = (pg_time_t) time(NULL); + + /* TODO: offloaded tasks go here */ + + /* Calculate how long to sleep */ + end_time = (pg_time_t) time(NULL); + elapsed_secs = end_time - start_time; + if (elapsed_secs >= CUSTODIAN_TIMEOUT_S) + continue; /* no sleep for us */ + cur_timeout = CUSTODIAN_TIMEOUT_S - elapsed_secs; + + (void) WaitLatch(MyLatch, + WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + cur_timeout * 1000L /* convert to ms */ , + WAIT_EVENT_CUSTODIAN_MAIN); + } + + pg_unreachable(); +} diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ce90877154..0911127471 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -250,6 +250,7 @@ bool remove_temp_files_after_crash = true; static pid_t StartupPID = 0, BgWriterPID = 0, CheckpointerPID = 0, + CustodianPID = 0, WalWriterPID = 0, WalReceiverPID = 0, AutoVacPID = 0, @@ -556,6 +557,7 @@ static void ShmemBackendArrayRemove(Backend *bn); #define StartArchiver() StartChildProcess(ArchiverProcess) #define StartBackgroundWriter() StartChildProcess(BgWriterProcess) #define StartCheckpointer() StartChildProcess(CheckpointerProcess) +#define StartCustodian() StartChildProcess(CustodianProcess) #define StartWalWriter() StartChildProcess(WalWriterProcess) #define StartWalReceiver() StartChildProcess(WalReceiverProcess) @@ -1817,13 +1819,16 @@ ServerLoop(void) /* * If no background writer process is running, and we are not in a * state that prevents it, start one. It doesn't matter if this - * fails, we'll just try again later. Likewise for the checkpointer. + * fails, we'll just try again later. Likewise for the checkpointer + * and custodian. */ if (pmState == PM_RUN || pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY || pmState == PM_STARTUP) { if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); } @@ -2780,6 +2785,8 @@ SIGHUP_handler(SIGNAL_ARGS) signal_child(BgWriterPID, SIGHUP); if (CheckpointerPID != 0) signal_child(CheckpointerPID, SIGHUP); + if (CustodianPID != 0) + signal_child(CustodianPID, SIGHUP); if (WalWriterPID != 0) signal_child(WalWriterPID, SIGHUP); if (WalReceiverPID != 0) @@ -3107,6 +3114,8 @@ reaper(SIGNAL_ARGS) */ if (CheckpointerPID == 0) CheckpointerPID = StartCheckpointer(); + if (CustodianPID == 0) + CustodianPID = StartCustodian(); if (BgWriterPID == 0) BgWriterPID = StartBackgroundWriter(); if (WalWriterPID == 0) @@ -3209,6 +3218,20 @@ reaper(SIGNAL_ARGS) continue; } + /* + * Was it the custodian? Normal exit can be ignored; we'll start a + * new one at the next iteration of the postmaster's main loop, if + * necessary. Any other exit condition is treated as a crash. + */ + if (pid == CustodianPID) + { + CustodianPID = 0; + if (!EXIT_STATUS_0(exitstatus)) + HandleChildCrash(pid, exitstatus, + _("custodian process")); + continue; + } + /* * Was it the wal writer? Normal exit can be ignored; we'll start a * new one at the next iteration of the postmaster's main loop, if @@ -3682,6 +3705,18 @@ HandleChildCrash(int pid, int exitstatus, const char *procname) signal_child(CheckpointerPID, (SendStop ? SIGSTOP : SIGQUIT)); } + /* Take care of the custodian too */ + if (pid == CustodianPID) + CustodianPID = 0; + else if (CustodianPID != 0 && take_action) + { + ereport(DEBUG2, + (errmsg_internal("sending %s to process %d", + (SendStop ? "SIGSTOP" : "SIGQUIT"), + (int) CustodianPID))); + signal_child(CustodianPID, (SendStop ? SIGSTOP : SIGQUIT)); + } + /* Take care of the walwriter too */ if (pid == WalWriterPID) WalWriterPID = 0; @@ -3885,6 +3920,9 @@ PostmasterStateMachine(void) /* and the bgwriter too */ if (BgWriterPID != 0) signal_child(BgWriterPID, SIGTERM); + /* and the custodian too */ + if (CustodianPID != 0) + signal_child(CustodianPID, SIGTERM); /* and the walwriter too */ if (WalWriterPID != 0) signal_child(WalWriterPID, SIGTERM); @@ -3922,6 +3960,7 @@ PostmasterStateMachine(void) BgWriterPID == 0 && (CheckpointerPID == 0 || (!FatalError && Shutdown < ImmediateShutdown)) && + CustodianPID == 0 && WalWriterPID == 0 && AutoVacPID == 0) { @@ -4015,6 +4054,7 @@ PostmasterStateMachine(void) Assert(WalReceiverPID == 0); Assert(BgWriterPID == 0); Assert(CheckpointerPID == 0); + Assert(CustodianPID == 0); Assert(WalWriterPID == 0); Assert(AutoVacPID == 0); /* syslogger is not considered here */ @@ -4220,6 +4260,8 @@ TerminateChildren(int signal) signal_child(BgWriterPID, signal); if (CheckpointerPID != 0) signal_child(CheckpointerPID, signal); + if (CustodianPID != 0) + signal_child(CustodianPID, signal); if (WalWriterPID != 0) signal_child(WalWriterPID, signal); if (WalReceiverPID != 0) diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 37f032e7b9..f9df0259fd 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -181,6 +181,7 @@ InitProcGlobal(void) ProcGlobal->startupBufferPinWaitBufId = -1; ProcGlobal->walwriterLatch = NULL; ProcGlobal->checkpointerLatch = NULL; + ProcGlobal->custodianLatch = NULL; pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PGPROCNO); pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PGPROCNO); diff --git a/src/backend/utils/activity/wait_event.c b/src/backend/utils/activity/wait_event.c index 60972c3a75..e10cc2d82b 100644 --- a/src/backend/utils/activity/wait_event.c +++ b/src/backend/utils/activity/wait_event.c @@ -224,6 +224,9 @@ pgstat_get_wait_activity(WaitEventActivity w) case WAIT_EVENT_CHECKPOINTER_MAIN: event_name = "CheckpointerMain"; break; + case WAIT_EVENT_CUSTODIAN_MAIN: + event_name = "CustodianMain"; + break; case WAIT_EVENT_LOGICAL_APPLY_MAIN: event_name = "LogicalApplyMain"; break; diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 0868e5a24f..8b52757ea6 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -274,6 +274,9 @@ GetBackendTypeDesc(BackendType backendType) case B_CHECKPOINTER: backendDesc = "checkpointer"; break; + case B_CUSTODIAN: + backendDesc = "custodian"; + break; case B_STARTUP: backendDesc = "startup"; break; diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index 0abc3ad540..71f522878e 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -328,6 +328,7 @@ typedef enum BackendType B_BG_WORKER, B_BG_WRITER, B_CHECKPOINTER, + B_CUSTODIAN, B_STARTUP, B_WAL_RECEIVER, B_WAL_SENDER, @@ -432,6 +433,7 @@ typedef enum BgWriterProcess, ArchiverProcess, CheckpointerProcess, + CustodianProcess, WalWriterProcess, WalReceiverProcess, @@ -444,6 +446,7 @@ extern AuxProcType MyAuxProcType; #define AmBackgroundWriterProcess() (MyAuxProcType == BgWriterProcess) #define AmArchiverProcess() (MyAuxProcType == ArchiverProcess) #define AmCheckpointerProcess() (MyAuxProcType == CheckpointerProcess) +#define AmCustodianProcess() (MyAuxProcType == CustodianProcess) #define AmWalWriterProcess() (MyAuxProcType == WalWriterProcess) #define AmWalReceiverProcess() (MyAuxProcType == WalReceiverProcess) diff --git a/src/include/postmaster/custodian.h b/src/include/postmaster/custodian.h new file mode 100644 index 0000000000..cf0a04ca6c --- /dev/null +++ b/src/include/postmaster/custodian.h @@ -0,0 +1,17 @@ +/*------------------------------------------------------------------------- + * + * custodian.h + * Exports from postmaster/custodian.c. + * + * Copyright (c) 2022, PostgreSQL Global Development Group + * + * src/include/postmaster/custodian.h + * + *------------------------------------------------------------------------- + */ +#ifndef _CUSTODIAN_H +#define _CUSTODIAN_H + +extern void CustodianMain(void) pg_attribute_noreturn(); + +#endif /* _CUSTODIAN_H */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index a58888f9e9..ad61b4d802 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -357,6 +357,8 @@ typedef struct PROC_HDR Latch *walwriterLatch; /* Checkpointer process's latch */ Latch *checkpointerLatch; + /* Custodian process's latch */ + Latch *custodianLatch; /* Current shared estimate of appropriate spins_per_delay value */ int spins_per_delay; /* Buffer id of the buffer that Startup process waits for pin on, or -1 */ @@ -374,11 +376,12 @@ extern PGPROC *PreparedXactProcs; * We set aside some extra PGPROC structures for auxiliary processes, * ie things that aren't full-fledged backends but need shmem access. * - * Background writer, checkpointer, WAL writer and archiver run during normal - * operation. Startup process and WAL receiver also consume 2 slots, but WAL - * writer is launched only after startup has exited, so we only need 5 slots. + * Background writer, checkpointer, custodian, WAL writer and archiver run + * during normal operation. Startup process and WAL receiver also consume 2 + * slots, but WAL writer is launched only after startup has exited, so we only + * need 6 slots. */ -#define NUM_AUXILIARY_PROCS 5 +#define NUM_AUXILIARY_PROCS 6 /* configurable options */ extern PGDLLIMPORT int DeadlockTimeout; diff --git a/src/include/utils/wait_event.h b/src/include/utils/wait_event.h index 395d325c5f..1338d06823 100644 --- a/src/include/utils/wait_event.h +++ b/src/include/utils/wait_event.h @@ -40,6 +40,7 @@ typedef enum WAIT_EVENT_BGWRITER_HIBERNATE, WAIT_EVENT_BGWRITER_MAIN, WAIT_EVENT_CHECKPOINTER_MAIN, + WAIT_EVENT_CUSTODIAN_MAIN, WAIT_EVENT_LOGICAL_APPLY_MAIN, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN, WAIT_EVENT_PGSTAT_MAIN, -- 2.25.1 --BXVAT5kNtrzKuDFl Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v4-0002-Also-remove-pgsql_tmp-directories-during-startup.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
* [PATCH v8 3/3] Use new overflow-aware integer comparison functions. @ 2024-02-15 23:28 Nathan Bossart <nathan@postgresql.org> 0 siblings, 0 replies; 24+ messages in thread From: Nathan Bossart @ 2024-02-15 23:28 UTC (permalink / raw) Commit XXXXXXXXXX introduced integer comparison functions designed to be as efficient as possible while avoiding overflow. This commit makes use of these functions in many of the in-tree qsort() comparators, which helps ensure transitivity. The comparator functions that previously had trivial implementations (i.e., "if" statements) should also see a small performance boost. Author: Mats Kindahl Reviewed-by: Tom Lane, Heikki Linnakangas, Andres Freund, Fabrízio de Royes Mello Discussion: https://postgr.es/m/CA%2B14426g2Wa9QuUpmakwPxXFWG_1FaY0AsApkvcTBy-YfS6uaw%40mail.gmail.com --- contrib/hstore/hstore_gist.c | 4 +++- contrib/intarray/_int_tool.c | 9 +++------ contrib/intarray/_intbig_gist.c | 4 +++- contrib/pg_stat_statements/pg_stat_statements.c | 8 ++------ contrib/pg_trgm/trgm_op.c | 8 ++------ src/backend/access/nbtree/nbtinsert.c | 8 ++------ src/backend/access/nbtree/nbtpage.c | 10 +++------- src/backend/access/nbtree/nbtsplitloc.c | 8 ++------ src/backend/access/spgist/spgdoinsert.c | 5 ++--- src/backend/access/spgist/spgtextproc.c | 3 ++- src/backend/backup/basebackup_incremental.c | 8 ++------ src/backend/backup/walsummary.c | 7 ++----- src/backend/catalog/heap.c | 7 ++----- src/backend/nodes/list.c | 13 +++---------- src/backend/nodes/tidbitmap.c | 7 ++----- src/backend/parser/parse_agg.c | 3 ++- src/backend/postmaster/autovacuum.c | 7 +++---- src/backend/replication/logical/reorderbuffer.c | 7 ++----- src/backend/replication/syncrep.c | 8 ++------ src/backend/utils/adt/oid.c | 7 ++----- src/backend/utils/adt/tsgistidx.c | 10 +++------- src/backend/utils/adt/tsquery_gist.c | 7 +++---- src/backend/utils/adt/tsvector.c | 5 ++--- src/backend/utils/adt/tsvector_op.c | 5 ++--- src/backend/utils/adt/xid.c | 7 ++----- src/backend/utils/cache/relcache.c | 3 ++- src/backend/utils/cache/syscache.c | 5 ++--- src/backend/utils/cache/typcache.c | 8 ++------ src/backend/utils/resowner/resowner.c | 10 ++-------- src/bin/pg_dump/pg_dump_sort.c | 7 ++----- src/bin/pg_upgrade/function.c | 12 ++++++------ src/bin/pg_walsummary/pg_walsummary.c | 8 ++------ src/bin/psql/crosstabview.c | 3 ++- src/include/access/gin_private.h | 8 ++------ 34 files changed, 80 insertions(+), 159 deletions(-) diff --git a/contrib/hstore/hstore_gist.c b/contrib/hstore/hstore_gist.c index fe343739eb..a3b08af385 100644 --- a/contrib/hstore/hstore_gist.c +++ b/contrib/hstore/hstore_gist.c @@ -7,6 +7,7 @@ #include "access/reloptions.h" #include "access/stratnum.h" #include "catalog/pg_type.h" +#include "common/int.h" #include "hstore.h" #include "utils/pg_crc.h" @@ -356,7 +357,8 @@ typedef struct static int comparecost(const void *a, const void *b) { - return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; + return pg_cmp_s32(((const SPLITCOST *) a)->cost, + ((const SPLITCOST *) b)->cost); } diff --git a/contrib/intarray/_int_tool.c b/contrib/intarray/_int_tool.c index 68f624e085..c85280c842 100644 --- a/contrib/intarray/_int_tool.c +++ b/contrib/intarray/_int_tool.c @@ -7,6 +7,7 @@ #include "_int.h" #include "catalog/pg_type.h" +#include "common/int.h" #include "lib/qunique.h" /* arguments are assumed sorted & unique-ified */ @@ -396,15 +397,11 @@ int_to_intset(int32 elem) int compASC(const void *a, const void *b) { - if (*(const int32 *) a == *(const int32 *) b) - return 0; - return (*(const int32 *) a > *(const int32 *) b) ? 1 : -1; + return pg_cmp_s32(*(const int32 *) a, *(const int32 *) b); } int compDESC(const void *a, const void *b) { - if (*(const int32 *) a == *(const int32 *) b) - return 0; - return (*(const int32 *) a < *(const int32 *) b) ? 1 : -1; + return pg_cmp_s32(*(const int32 *) b, *(const int32 *) a); } diff --git a/contrib/intarray/_intbig_gist.c b/contrib/intarray/_intbig_gist.c index 8c6c4b5d05..9699fbf3b4 100644 --- a/contrib/intarray/_intbig_gist.c +++ b/contrib/intarray/_intbig_gist.c @@ -9,6 +9,7 @@ #include "access/gist.h" #include "access/reloptions.h" #include "access/stratnum.h" +#include "common/int.h" #include "port/pg_bitutils.h" #define GETENTRY(vec,pos) ((GISTTYPE *) DatumGetPointer((vec)->vector[(pos)].key)) @@ -312,7 +313,8 @@ typedef struct static int comparecost(const void *a, const void *b) { - return ((const SPLITCOST *) a)->cost - ((const SPLITCOST *) b)->cost; + return pg_cmp_s32(((const SPLITCOST *) a)->cost, + ((const SPLITCOST *) b)->cost); } diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index 8c6a3a2d08..67cec865ba 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -50,6 +50,7 @@ #include "access/parallel.h" #include "catalog/pg_authid.h" #include "common/hashfn.h" +#include "common/int.h" #include "executor/instrument.h" #include "funcapi.h" #include "jit/jit.h" @@ -3007,10 +3008,5 @@ comp_location(const void *a, const void *b) int l = ((const LocationLen *) a)->location; int r = ((const LocationLen *) b)->location; - if (l < r) - return -1; - else if (l > r) - return +1; - else - return 0; + return pg_cmp_s32(l, r); } diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c index 49d4497b4f..c509d15ee4 100644 --- a/contrib/pg_trgm/trgm_op.c +++ b/contrib/pg_trgm/trgm_op.c @@ -6,6 +6,7 @@ #include <ctype.h> #include "catalog/pg_type.h" +#include "common/int.h" #include "lib/qunique.h" #include "miscadmin.h" #include "trgm.h" @@ -433,12 +434,7 @@ comp_ptrgm(const void *v1, const void *v2) if (cmp != 0) return cmp; - if (p1->index < p2->index) - return -1; - else if (p1->index == p2->index) - return 0; - else - return 1; + return pg_cmp_s32(p1->index, p2->index); } /* diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c index 709edd1c17..e9cfc13604 100644 --- a/src/backend/access/nbtree/nbtinsert.c +++ b/src/backend/access/nbtree/nbtinsert.c @@ -19,6 +19,7 @@ #include "access/nbtxlog.h" #include "access/transam.h" #include "access/xloginsert.h" +#include "common/int.h" #include "common/pg_prng.h" #include "lib/qunique.h" #include "miscadmin.h" @@ -3013,10 +3014,5 @@ _bt_blk_cmp(const void *arg1, const void *arg2) BlockNumber b1 = *((BlockNumber *) arg1); BlockNumber b2 = *((BlockNumber *) arg2); - if (b1 < b2) - return -1; - else if (b1 > b2) - return 1; - - return 0; + return pg_cmp_u32(b1, b2); } diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index 567bade9f4..90990ea77a 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -28,6 +28,7 @@ #include "access/transam.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "common/int.h" #include "miscadmin.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" @@ -1466,14 +1467,9 @@ _bt_delitems_cmp(const void *a, const void *b) TM_IndexDelete *indexdelete1 = (TM_IndexDelete *) a; TM_IndexDelete *indexdelete2 = (TM_IndexDelete *) b; - if (indexdelete1->id > indexdelete2->id) - return 1; - if (indexdelete1->id < indexdelete2->id) - return -1; + Assert(indexdelete1->id != indexdelete2->id); - Assert(false); - - return 0; + return pg_cmp_s16(indexdelete1->id, indexdelete2->id); } /* diff --git a/src/backend/access/nbtree/nbtsplitloc.c b/src/backend/access/nbtree/nbtsplitloc.c index d0b1d82578..490e7bfd4d 100644 --- a/src/backend/access/nbtree/nbtsplitloc.c +++ b/src/backend/access/nbtree/nbtsplitloc.c @@ -15,6 +15,7 @@ #include "postgres.h" #include "access/nbtree.h" +#include "common/int.h" #include "storage/lmgr.h" typedef enum @@ -596,12 +597,7 @@ _bt_splitcmp(const void *arg1, const void *arg2) SplitPoint *split1 = (SplitPoint *) arg1; SplitPoint *split2 = (SplitPoint *) arg2; - if (split1->curdelta > split2->curdelta) - return 1; - if (split1->curdelta < split2->curdelta) - return -1; - - return 0; + return pg_cmp_s16(split1->curdelta, split2->curdelta); } /* diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c index bb063c858d..a4995c168b 100644 --- a/src/backend/access/spgist/spgdoinsert.c +++ b/src/backend/access/spgist/spgdoinsert.c @@ -19,6 +19,7 @@ #include "access/spgist_private.h" #include "access/spgxlog.h" #include "access/xloginsert.h" +#include "common/int.h" #include "common/pg_prng.h" #include "miscadmin.h" #include "storage/bufmgr.h" @@ -110,9 +111,7 @@ addNode(SpGistState *state, SpGistInnerTuple tuple, Datum label, int offset) static int cmpOffsetNumbers(const void *a, const void *b) { - if (*(const OffsetNumber *) a == *(const OffsetNumber *) b) - return 0; - return (*(const OffsetNumber *) a > *(const OffsetNumber *) b) ? 1 : -1; + return pg_cmp_u16(*(const OffsetNumber *) a, *(const OffsetNumber *) b); } /* diff --git a/src/backend/access/spgist/spgtextproc.c b/src/backend/access/spgist/spgtextproc.c index b8fd0c2ad8..d5db5225a9 100644 --- a/src/backend/access/spgist/spgtextproc.c +++ b/src/backend/access/spgist/spgtextproc.c @@ -40,6 +40,7 @@ #include "postgres.h" #include "access/spgist.h" +#include "common/int.h" #include "catalog/pg_type.h" #include "mb/pg_wchar.h" #include "utils/builtins.h" @@ -325,7 +326,7 @@ cmpNodePtr(const void *a, const void *b) const spgNodePtr *aa = (const spgNodePtr *) a; const spgNodePtr *bb = (const spgNodePtr *) b; - return aa->c - bb->c; + return pg_cmp_s16(aa->c, bb->c); } Datum diff --git a/src/backend/backup/basebackup_incremental.c b/src/backend/backup/basebackup_incremental.c index 0504c465db..e994ee66bb 100644 --- a/src/backend/backup/basebackup_incremental.c +++ b/src/backend/backup/basebackup_incremental.c @@ -27,6 +27,7 @@ #include "common/blkreftable.h" #include "common/parse_manifest.h" #include "common/hashfn.h" +#include "common/int.h" #include "postmaster/walsummarizer.h" #define BLOCKS_PER_READ 512 @@ -994,10 +995,5 @@ compare_block_numbers(const void *a, const void *b) BlockNumber aa = *(BlockNumber *) a; BlockNumber bb = *(BlockNumber *) b; - if (aa > bb) - return 1; - else if (aa == bb) - return 0; - else - return -1; + return pg_cmp_u32(aa, bb); } diff --git a/src/backend/backup/walsummary.c b/src/backend/backup/walsummary.c index 867870aaad..4d047e1c02 100644 --- a/src/backend/backup/walsummary.c +++ b/src/backend/backup/walsummary.c @@ -17,6 +17,7 @@ #include "access/xlog_internal.h" #include "backup/walsummary.h" +#include "common/int.h" #include "utils/wait_event.h" static bool IsWalSummaryFilename(char *filename); @@ -355,9 +356,5 @@ ListComparatorForWalSummaryFiles(const ListCell *a, const ListCell *b) WalSummaryFile *ws1 = lfirst(a); WalSummaryFile *ws2 = lfirst(b); - if (ws1->start_lsn < ws2->start_lsn) - return -1; - if (ws1->start_lsn > ws2->start_lsn) - return 1; - return 0; + return pg_cmp_u64(ws1->start_lsn, ws2->start_lsn); } diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index c73f7bcd01..03368b93d7 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -56,6 +56,7 @@ #include "catalog/storage.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" +#include "common/int.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" @@ -2759,11 +2760,7 @@ list_cookedconstr_attnum_cmp(const ListCell *p1, const ListCell *p2) AttrNumber v1 = ((CookedConstraint *) lfirst(p1))->attnum; AttrNumber v2 = ((CookedConstraint *) lfirst(p2))->attnum; - if (v1 < v2) - return -1; - if (v1 > v2) - return 1; - return 0; + return pg_cmp_s16(v1, v2); } /* diff --git a/src/backend/nodes/list.c b/src/backend/nodes/list.c index 957186bef5..e2615ab105 100644 --- a/src/backend/nodes/list.c +++ b/src/backend/nodes/list.c @@ -17,6 +17,7 @@ */ #include "postgres.h" +#include "common/int.h" #include "nodes/pg_list.h" #include "port/pg_bitutils.h" #include "utils/memdebug.h" @@ -1692,11 +1693,7 @@ list_int_cmp(const ListCell *p1, const ListCell *p2) int v1 = lfirst_int(p1); int v2 = lfirst_int(p2); - if (v1 < v2) - return -1; - if (v1 > v2) - return 1; - return 0; + return pg_cmp_s32(v1, v2); } /* @@ -1708,9 +1705,5 @@ list_oid_cmp(const ListCell *p1, const ListCell *p2) Oid v1 = lfirst_oid(p1); Oid v2 = lfirst_oid(p2); - if (v1 < v2) - return -1; - if (v1 > v2) - return 1; - return 0; + return pg_cmp_u32(v1, v2); } diff --git a/src/backend/nodes/tidbitmap.c b/src/backend/nodes/tidbitmap.c index 0f4850065f..e8ab5d78fc 100644 --- a/src/backend/nodes/tidbitmap.c +++ b/src/backend/nodes/tidbitmap.c @@ -42,6 +42,7 @@ #include "access/htup_details.h" #include "common/hashfn.h" +#include "common/int.h" #include "nodes/bitmapset.h" #include "nodes/tidbitmap.h" #include "storage/lwlock.h" @@ -1425,11 +1426,7 @@ tbm_comparator(const void *left, const void *right) BlockNumber l = (*((PagetableEntry *const *) left))->blockno; BlockNumber r = (*((PagetableEntry *const *) right))->blockno; - if (l < r) - return -1; - else if (l > r) - return 1; - return 0; + return pg_cmp_u32(l, r); } /* diff --git a/src/backend/parser/parse_agg.c b/src/backend/parser/parse_agg.c index 7b211a7743..9d151a880b 100644 --- a/src/backend/parser/parse_agg.c +++ b/src/backend/parser/parse_agg.c @@ -18,6 +18,7 @@ #include "catalog/pg_aggregate.h" #include "catalog/pg_constraint.h" #include "catalog/pg_type.h" +#include "common/int.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "optimizer/optimizer.h" @@ -1760,7 +1761,7 @@ cmp_list_len_asc(const ListCell *a, const ListCell *b) int la = list_length((const List *) lfirst(a)); int lb = list_length((const List *) lfirst(b)); - return (la > lb) ? 1 : (la == lb) ? 0 : -1; + return pg_cmp_s32(la, lb); } /* list_sort comparator to sort sub-lists by length and contents */ diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 37998f7387..2ab344c1f8 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -78,6 +78,7 @@ #include "catalog/pg_database.h" #include "commands/dbcommands.h" #include "commands/vacuum.h" +#include "common/int.h" #include "lib/ilist.h" #include "libpq/pqsignal.h" #include "miscadmin.h" @@ -1120,10 +1121,8 @@ rebuild_database_list(Oid newdb) static int db_comparator(const void *a, const void *b) { - if (((const avl_dbase *) a)->adl_score == ((const avl_dbase *) b)->adl_score) - return 0; - else - return (((const avl_dbase *) a)->adl_score < ((const avl_dbase *) b)->adl_score) ? 1 : -1; + return pg_cmp_s32(((const avl_dbase *) a)->adl_score, + ((const avl_dbase *) b)->adl_score); } /* diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c index bbf0966182..5446df3c64 100644 --- a/src/backend/replication/logical/reorderbuffer.c +++ b/src/backend/replication/logical/reorderbuffer.c @@ -91,6 +91,7 @@ #include "access/xact.h" #include "access/xlog_internal.h" #include "catalog/catalog.h" +#include "common/int.h" #include "lib/binaryheap.h" #include "miscadmin.h" #include "pgstat.h" @@ -5119,11 +5120,7 @@ file_sort_by_lsn(const ListCell *a_p, const ListCell *b_p) RewriteMappingFile *a = (RewriteMappingFile *) lfirst(a_p); RewriteMappingFile *b = (RewriteMappingFile *) lfirst(b_p); - if (a->lsn < b->lsn) - return -1; - else if (a->lsn > b->lsn) - return 1; - return 0; + return pg_cmp_u64(a->lsn, b->lsn); } /* diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 2e6493aaaa..bfcd8fa13e 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -75,6 +75,7 @@ #include <unistd.h> #include "access/xact.h" +#include "common/int.h" #include "miscadmin.h" #include "pgstat.h" #include "replication/syncrep.h" @@ -698,12 +699,7 @@ cmp_lsn(const void *a, const void *b) XLogRecPtr lsn1 = *((const XLogRecPtr *) a); XLogRecPtr lsn2 = *((const XLogRecPtr *) b); - if (lsn1 > lsn2) - return -1; - else if (lsn1 == lsn2) - return 0; - else - return 1; + return pg_cmp_u64(lsn2, lsn1); } /* diff --git a/src/backend/utils/adt/oid.c b/src/backend/utils/adt/oid.c index 62bcfc5b56..56fb1fd77c 100644 --- a/src/backend/utils/adt/oid.c +++ b/src/backend/utils/adt/oid.c @@ -18,6 +18,7 @@ #include <limits.h> #include "catalog/pg_type.h" +#include "common/int.h" #include "libpq/pqformat.h" #include "nodes/miscnodes.h" #include "nodes/value.h" @@ -259,11 +260,7 @@ oid_cmp(const void *p1, const void *p2) Oid v1 = *((const Oid *) p1); Oid v2 = *((const Oid *) p2); - if (v1 < v2) - return -1; - if (v1 > v2) - return 1; - return 0; + return pg_cmp_u32(v1, v2); } diff --git a/src/backend/utils/adt/tsgistidx.c b/src/backend/utils/adt/tsgistidx.c index a62b285365..3fb7696434 100644 --- a/src/backend/utils/adt/tsgistidx.c +++ b/src/backend/utils/adt/tsgistidx.c @@ -17,6 +17,7 @@ #include "access/gist.h" #include "access/heaptoast.h" #include "access/reloptions.h" +#include "common/int.h" #include "lib/qunique.h" #include "port/pg_bitutils.h" #include "tsearch/ts_utils.h" @@ -136,9 +137,7 @@ compareint(const void *va, const void *vb) int32 a = *((const int32 *) va); int32 b = *((const int32 *) vb); - if (a == b) - return 0; - return (a > b) ? 1 : -1; + return pg_cmp_s32(a, b); } static void @@ -598,10 +597,7 @@ comparecost(const void *va, const void *vb) const SPLITCOST *a = (const SPLITCOST *) va; const SPLITCOST *b = (const SPLITCOST *) vb; - if (a->cost == b->cost) - return 0; - else - return (a->cost > b->cost) ? 1 : -1; + return pg_cmp_s32(a->cost, b->cost); } diff --git a/src/backend/utils/adt/tsquery_gist.c b/src/backend/utils/adt/tsquery_gist.c index f0b1c81c81..2db304b10b 100644 --- a/src/backend/utils/adt/tsquery_gist.c +++ b/src/backend/utils/adt/tsquery_gist.c @@ -16,6 +16,7 @@ #include "access/gist.h" #include "access/stratnum.h" +#include "common/int.h" #include "tsearch/ts_utils.h" #include "utils/builtins.h" @@ -156,10 +157,8 @@ typedef struct static int comparecost(const void *a, const void *b) { - if (((const SPLITCOST *) a)->cost == ((const SPLITCOST *) b)->cost) - return 0; - else - return (((const SPLITCOST *) a)->cost > ((const SPLITCOST *) b)->cost) ? 1 : -1; + return pg_cmp_s32(((const SPLITCOST *) a)->cost, + ((const SPLITCOST *) b)->cost); } #define WISH_F(a,b,c) (double)( -(double)(((a)-(b))*((a)-(b))*((a)-(b)))*(c) ) diff --git a/src/backend/utils/adt/tsvector.c b/src/backend/utils/adt/tsvector.c index fb7b7c712a..10bc4f2234 100644 --- a/src/backend/utils/adt/tsvector.c +++ b/src/backend/utils/adt/tsvector.c @@ -14,6 +14,7 @@ #include "postgres.h" +#include "common/int.h" #include "libpq/pqformat.h" #include "nodes/miscnodes.h" #include "tsearch/ts_locale.h" @@ -37,9 +38,7 @@ compareWordEntryPos(const void *a, const void *b) int apos = WEP_GETPOS(*(const WordEntryPos *) a); int bpos = WEP_GETPOS(*(const WordEntryPos *) b); - if (apos == bpos) - return 0; - return (apos > bpos) ? 1 : -1; + return pg_cmp_s32(apos, bpos); } /* diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c index 71386d0a1f..947a592ed2 100644 --- a/src/backend/utils/adt/tsvector_op.c +++ b/src/backend/utils/adt/tsvector_op.c @@ -19,6 +19,7 @@ #include "catalog/namespace.h" #include "catalog/pg_type.h" #include "commands/trigger.h" +#include "common/int.h" #include "executor/spi.h" #include "funcapi.h" #include "lib/qunique.h" @@ -435,9 +436,7 @@ compare_int(const void *va, const void *vb) int a = *((const int *) va); int b = *((const int *) vb); - if (a == b) - return 0; - return (a > b) ? 1 : -1; + return pg_cmp_s32(a, b); } static int diff --git a/src/backend/utils/adt/xid.c b/src/backend/utils/adt/xid.c index 63adf5668b..ae273b1961 100644 --- a/src/backend/utils/adt/xid.c +++ b/src/backend/utils/adt/xid.c @@ -19,6 +19,7 @@ #include "access/multixact.h" #include "access/transam.h" #include "access/xact.h" +#include "common/int.h" #include "libpq/pqformat.h" #include "utils/builtins.h" #include "utils/xid8.h" @@ -140,11 +141,7 @@ xidComparator(const void *arg1, const void *arg2) TransactionId xid1 = *(const TransactionId *) arg1; TransactionId xid2 = *(const TransactionId *) arg2; - if (xid1 > xid2) - return 1; - if (xid1 < xid2) - return -1; - return 0; + return pg_cmp_u32(xid1, xid2); } /* diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index ac106b40e3..50acae4529 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -69,6 +69,7 @@ #include "commands/policy.h" #include "commands/publicationcmds.h" #include "commands/trigger.h" +#include "common/int.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" @@ -4520,7 +4521,7 @@ AttrDefaultCmp(const void *a, const void *b) const AttrDefault *ada = (const AttrDefault *) a; const AttrDefault *adb = (const AttrDefault *) b; - return ada->adnum - adb->adnum; + return pg_cmp_s16(ada->adnum, adb->adnum); } /* diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index 662f930864..2292237f85 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -29,6 +29,7 @@ #include "catalog/pg_shdepend_d.h" #include "catalog/pg_shdescription_d.h" #include "catalog/pg_shseclabel_d.h" +#include "common/int.h" #include "lib/qunique.h" #include "utils/catcache.h" #include "utils/lsyscache.h" @@ -676,7 +677,5 @@ oid_compare(const void *a, const void *b) Oid oa = *((const Oid *) a); Oid ob = *((const Oid *) b); - if (oa == ob) - return 0; - return (oa > ob) ? 1 : -1; + return pg_cmp_u32(oa, ob); } diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c index 84fc83cb11..2842bde907 100644 --- a/src/backend/utils/cache/typcache.c +++ b/src/backend/utils/cache/typcache.c @@ -57,6 +57,7 @@ #include "catalog/pg_range.h" #include "catalog/pg_type.h" #include "commands/defrem.h" +#include "common/int.h" #include "executor/executor.h" #include "lib/dshash.h" #include "optimizer/optimizer.h" @@ -2722,12 +2723,7 @@ enum_oid_cmp(const void *left, const void *right) const EnumItem *l = (const EnumItem *) left; const EnumItem *r = (const EnumItem *) right; - if (l->enum_oid < r->enum_oid) - return -1; - else if (l->enum_oid > r->enum_oid) - return 1; - else - return 0; + return pg_cmp_u32(l->enum_oid, r->enum_oid); } /* diff --git a/src/backend/utils/resowner/resowner.c b/src/backend/utils/resowner/resowner.c index aa199b23ff..ab9343bc5c 100644 --- a/src/backend/utils/resowner/resowner.c +++ b/src/backend/utils/resowner/resowner.c @@ -46,6 +46,7 @@ #include "postgres.h" #include "common/hashfn.h" +#include "common/int.h" #include "storage/ipc.h" #include "storage/predicate.h" #include "storage/proc.h" @@ -264,14 +265,7 @@ resource_priority_cmp(const void *a, const void *b) /* Note: reverse order */ if (ra->kind->release_phase == rb->kind->release_phase) - { - if (ra->kind->release_priority == rb->kind->release_priority) - return 0; - else if (ra->kind->release_priority > rb->kind->release_priority) - return -1; - else - return 1; - } + return pg_cmp_u32(rb->kind->release_priority, ra->kind->release_priority); else if (ra->kind->release_phase > rb->kind->release_phase) return -1; else diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index f358dd22b9..8ee8a42781 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -16,6 +16,7 @@ #include "postgres_fe.h" #include "catalog/pg_class_d.h" +#include "common/int.h" #include "lib/binaryheap.h" #include "pg_backup_archiver.h" #include "pg_backup_utils.h" @@ -1504,9 +1505,5 @@ int_cmp(void *a, void *b, void *arg) int ai = (int) (intptr_t) a; int bi = (int) (intptr_t) b; - if (ai < bi) - return -1; - if (ai > bi) - return 1; - return 0; + return pg_cmp_s32(ai, bi); } diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c index af998f74d3..d65153de81 100644 --- a/src/bin/pg_upgrade/function.c +++ b/src/bin/pg_upgrade/function.c @@ -11,6 +11,7 @@ #include "access/transam.h" #include "catalog/pg_language_d.h" +#include "common/int.h" #include "pg_upgrade.h" /* @@ -29,17 +30,16 @@ library_name_compare(const void *p1, const void *p2) { const char *str1 = ((const LibraryInfo *) p1)->name; const char *str2 = ((const LibraryInfo *) p2)->name; - int slen1 = strlen(str1); - int slen2 = strlen(str2); + size_t slen1 = strlen(str1); + size_t slen2 = strlen(str2); int cmp = strcmp(str1, str2); if (slen1 != slen2) - return slen1 - slen2; + return pg_cmp_size(slen1, slen2); if (cmp != 0) return cmp; - else - return ((const LibraryInfo *) p1)->dbnum - - ((const LibraryInfo *) p2)->dbnum; + return pg_cmp_s32(((const LibraryInfo *) p1)->dbnum, + ((const LibraryInfo *) p2)->dbnum); } diff --git a/src/bin/pg_walsummary/pg_walsummary.c b/src/bin/pg_walsummary/pg_walsummary.c index 1341c83c69..485c72d939 100644 --- a/src/bin/pg_walsummary/pg_walsummary.c +++ b/src/bin/pg_walsummary/pg_walsummary.c @@ -16,6 +16,7 @@ #include <limits.h> #include "common/blkreftable.h" +#include "common/int.h" #include "common/logging.h" #include "fe_utils/option_utils.h" #include "lib/stringinfo.h" @@ -219,12 +220,7 @@ compare_block_numbers(const void *a, const void *b) BlockNumber aa = *(BlockNumber *) a; BlockNumber bb = *(BlockNumber *) b; - if (aa > bb) - return 1; - else if (aa == bb) - return 0; - else - return -1; + return pg_cmp_u32(aa, bb); } /* diff --git a/src/bin/psql/crosstabview.c b/src/bin/psql/crosstabview.c index c6116b7238..305ed4ab0a 100644 --- a/src/bin/psql/crosstabview.c +++ b/src/bin/psql/crosstabview.c @@ -8,6 +8,7 @@ #include "postgres_fe.h" #include "common.h" +#include "common/int.h" #include "common/logging.h" #include "crosstabview.h" #include "pqexpbuffer.h" @@ -709,5 +710,5 @@ pivotFieldCompare(const void *a, const void *b) static int rankCompare(const void *a, const void *b) { - return *((const int *) a) - *((const int *) b); + return pg_cmp_s32(*(const int *) a, *(const int *) b); } diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 51d0c74a6b..3013a44bae 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -14,6 +14,7 @@ #include "access/gin.h" #include "access/ginblock.h" #include "access/itup.h" +#include "common/int.h" #include "catalog/pg_am_d.h" #include "fmgr.h" #include "lib/rbtree.h" @@ -489,12 +490,7 @@ ginCompareItemPointers(ItemPointer a, ItemPointer b) uint64 ia = (uint64) GinItemPointerGetBlockNumber(a) << 32 | GinItemPointerGetOffsetNumber(a); uint64 ib = (uint64) GinItemPointerGetBlockNumber(b) << 32 | GinItemPointerGetOffsetNumber(b); - if (ia == ib) - return 0; - else if (ia > ib) - return 1; - else - return -1; + return pg_cmp_u64(ia, ib); } extern int ginTraverseLock(Buffer buffer, bool searchMode); -- 2.25.1 --AhhlLboLdkugWU4S-- ^ permalink raw reply [nested|flat] 24+ messages in thread
end of thread, other threads:[~2024-02-15 23:28 UTC | newest] Thread overview: 24+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2022-01-05 19:24 [PATCH v6 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v17 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v12 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v13 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v9 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v7 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v11 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v17 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v16 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v19 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v15 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v13 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v4 1/8] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v14 1/3] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v14 1/3] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v8 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v12 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v5 1/8] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v20 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v15 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v16 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v10 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v18 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2024-02-15 23:28 [PATCH v8 3/3] Use new overflow-aware integer comparison functions. Nathan Bossart <nathan@postgresql.org>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox