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 v9a 09/22] ci: mingw: Don't rely on zstd implicitly being installed @ 2026-06-03 06:22 Andres Freund <andres@anarazel.de> 0 siblings, 0 replies; 24+ messages in thread From: Andres Freund @ 2026-06-03 06:22 UTC (permalink / raw) --- .github/workflows/pg-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pg-ci.yml b/.github/workflows/pg-ci.yml index 9611686af90..f2543689fe5 100644 --- a/.github/workflows/pg-ci.yml +++ b/.github/workflows/pg-ci.yml @@ -906,6 +906,7 @@ jobs: ${MINGW_PACKAGE_PREFIX}-perl \ ${MINGW_PACKAGE_PREFIX}-pkgconf \ ${MINGW_PACKAGE_PREFIX}-readline \ + ${MINGW_PACKAGE_PREFIX}-zstd \ ${MINGW_PACKAGE_PREFIX}-zlib - *nix_sysinfo_step -- 2.54.0.380.gc69baaf57b --lyfxwjjve3vodszg Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v9a-0010-ci-windows-Check-for-errors-cmd-powershell-don-t.patch" ^ permalink raw reply [nested|flat] 24+ messages in thread
end of thread, other threads:[~2026-06-03 06:22 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 v13 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 v19 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 v15 1/4] 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 v17 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v6 1/6] 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 v11 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 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 v14 1/3] 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 v18 1/4] 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 v17 1/4] 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 v16 1/4] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2022-01-05 19:24 [PATCH v8 1/6] Introduce custodian. Nathan Bossart <bossartn@amazon.com> 2026-06-03 06:22 [PATCH v9a 09/22] ci: mingw: Don't rely on zstd implicitly being installed Andres Freund <andres@anarazel.de>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox