aboutsummaryrefslogtreecommitdiff
path: root/src/backend/postmaster/pmchild.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/backend/postmaster/pmchild.c')
-rw-r--r--src/backend/postmaster/pmchild.c285
1 files changed, 285 insertions, 0 deletions
diff --git a/src/backend/postmaster/pmchild.c b/src/backend/postmaster/pmchild.c
new file mode 100644
index 00000000000..381cf005a9b
--- /dev/null
+++ b/src/backend/postmaster/pmchild.c
@@ -0,0 +1,285 @@
+/*-------------------------------------------------------------------------
+ *
+ * pmchild.c
+ * Functions for keeping track of postmaster child processes.
+ *
+ * Postmaster keeps track of all child processes so that when a process exits,
+ * it knows what kind of a process it was and can clean up accordingly. Every
+ * child process is allocated a PMChild struct from a fixed pool of structs.
+ * The size of the pool is determined by various settings that configure how
+ * many worker processes and backend connections are allowed, i.e.
+ * autovacuum_max_workers, max_worker_processes, max_wal_senders, and
+ * max_connections.
+ *
+ * Dead-end backends are handled slightly differently. There is no limit
+ * on the number of dead-end backends, and they do not need unique IDs, so
+ * their PMChild structs are allocated dynamically, not from a pool.
+ *
+ * The structures and functions in this file are private to the postmaster
+ * process. But note that there is an array in shared memory, managed by
+ * pmsignal.c, that mirrors this.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/postmaster/pmchild.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "miscadmin.h"
+#include "postmaster/autovacuum.h"
+#include "postmaster/postmaster.h"
+#include "replication/walsender.h"
+#include "storage/pmsignal.h"
+#include "storage/proc.h"
+
+/*
+ * Freelists for different kinds of child processes. We maintain separate
+ * pools for each, so that for example launching a lot of regular backends
+ * cannot prevent autovacuum or an aux process from launching.
+ */
+typedef struct PMChildPool
+{
+ int size; /* number of PMChild slots reserved for this
+ * kind of processes */
+ int first_slotno; /* first slot belonging to this pool */
+ dlist_head freelist; /* currently unused PMChild entries */
+} PMChildPool;
+
+static PMChildPool pmchild_pools[BACKEND_NUM_TYPES];
+NON_EXEC_STATIC int num_pmchild_slots = 0;
+
+/*
+ * List of active child processes. This includes dead-end children.
+ */
+dlist_head ActiveChildList;
+
+/*
+ * MaxLivePostmasterChildren
+ *
+ * This reports the number of postmaster child processes that can be active.
+ * It includes all children except for dead-end children. This allows the
+ * array in shared memory (PMChildFlags) to have a fixed maximum size.
+ */
+int
+MaxLivePostmasterChildren(void)
+{
+ if (num_pmchild_slots == 0)
+ elog(ERROR, "PM child array not initialized yet");
+ return num_pmchild_slots;
+}
+
+/*
+ * Initialize at postmaster startup
+ *
+ * Note: This is not called on crash restart. We rely on PMChild entries to
+ * remain valid through the restart process. This is important because the
+ * syslogger survives through the crash restart process, so we must not
+ * invalidate its PMChild slot.
+ */
+void
+InitPostmasterChildSlots(void)
+{
+ int slotno;
+ PMChild *slots;
+
+ /*
+ * We allow more connections here than we can have backends because some
+ * might still be authenticating; they might fail auth, or some existing
+ * backend might exit before the auth cycle is completed. The exact
+ * MaxConnections limit is enforced when a new backend tries to join the
+ * PGPROC array.
+ *
+ * WAL senders start out as regular backends, so they share the same pool.
+ */
+ pmchild_pools[B_BACKEND].size = 2 * (MaxConnections + max_wal_senders);
+
+ pmchild_pools[B_AUTOVAC_WORKER].size = autovacuum_max_workers;
+ pmchild_pools[B_BG_WORKER].size = max_worker_processes;
+
+ /*
+ * There can be only one of each of these running at a time. They each
+ * get their own pool of just one entry.
+ */
+ pmchild_pools[B_AUTOVAC_LAUNCHER].size = 1;
+ pmchild_pools[B_SLOTSYNC_WORKER].size = 1;
+ pmchild_pools[B_ARCHIVER].size = 1;
+ pmchild_pools[B_BG_WRITER].size = 1;
+ pmchild_pools[B_CHECKPOINTER].size = 1;
+ pmchild_pools[B_STARTUP].size = 1;
+ pmchild_pools[B_WAL_RECEIVER].size = 1;
+ pmchild_pools[B_WAL_SUMMARIZER].size = 1;
+ pmchild_pools[B_WAL_WRITER].size = 1;
+ pmchild_pools[B_LOGGER].size = 1;
+
+ /* The rest of the pmchild_pools are left at zero size */
+
+ /* Count the total number of slots */
+ num_pmchild_slots = 0;
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ num_pmchild_slots += pmchild_pools[i].size;
+
+ /* Initialize them */
+ slots = palloc(num_pmchild_slots * sizeof(PMChild));
+ slotno = 0;
+ for (int btype = 0; btype < BACKEND_NUM_TYPES; btype++)
+ {
+ pmchild_pools[btype].first_slotno = slotno + 1;
+ dlist_init(&pmchild_pools[btype].freelist);
+
+ for (int j = 0; j < pmchild_pools[btype].size; j++)
+ {
+ slots[slotno].pid = 0;
+ slots[slotno].child_slot = slotno + 1;
+ slots[slotno].bkend_type = B_INVALID;
+ slots[slotno].rw = NULL;
+ slots[slotno].bgworker_notify = false;
+ dlist_push_tail(&pmchild_pools[btype].freelist, &slots[slotno].elem);
+ slotno++;
+ }
+ }
+ Assert(slotno == num_pmchild_slots);
+
+ /* Initialize other structures */
+ dlist_init(&ActiveChildList);
+}
+
+/*
+ * Allocate a PMChild entry for a postmaster child process of given type.
+ *
+ * The entry is taken from the right pool for the type.
+ *
+ * pmchild->child_slot in the returned struct is unique among all active child
+ * processes.
+ */
+PMChild *
+AssignPostmasterChildSlot(BackendType btype)
+{
+ dlist_head *freelist;
+ PMChild *pmchild;
+
+ if (pmchild_pools[btype].size == 0)
+ elog(ERROR, "cannot allocate a PMChild slot for backend type %d", btype);
+
+ freelist = &pmchild_pools[btype].freelist;
+ if (dlist_is_empty(freelist))
+ return NULL;
+
+ pmchild = dlist_container(PMChild, elem, dlist_pop_head_node(freelist));
+ pmchild->pid = 0;
+ pmchild->bkend_type = btype;
+ pmchild->rw = NULL;
+ pmchild->bgworker_notify = true;
+
+ /*
+ * pmchild->child_slot for each entry was initialized when the array of
+ * slots was allocated. Sanity check it.
+ */
+ if (!(pmchild->child_slot >= pmchild_pools[btype].first_slotno &&
+ pmchild->child_slot < pmchild_pools[btype].first_slotno + pmchild_pools[btype].size))
+ {
+ elog(ERROR, "pmchild freelist for backend type %d is corrupt",
+ pmchild->bkend_type);
+ }
+
+ dlist_push_head(&ActiveChildList, &pmchild->elem);
+
+ /* Update the status in the shared memory array */
+ MarkPostmasterChildSlotAssigned(pmchild->child_slot);
+
+ elog(DEBUG2, "assigned pm child slot %d for %s",
+ pmchild->child_slot, PostmasterChildName(btype));
+
+ return pmchild;
+}
+
+/*
+ * Allocate a PMChild struct for a dead-end backend. Dead-end children are
+ * not assigned a child_slot number. The struct is palloc'd; returns NULL if
+ * out of memory.
+ */
+PMChild *
+AllocDeadEndChild(void)
+{
+ PMChild *pmchild;
+
+ elog(DEBUG2, "allocating dead-end child");
+
+ pmchild = (PMChild *) palloc_extended(sizeof(PMChild), MCXT_ALLOC_NO_OOM);
+ if (pmchild)
+ {
+ pmchild->pid = 0;
+ pmchild->child_slot = 0;
+ pmchild->bkend_type = B_DEAD_END_BACKEND;
+ pmchild->rw = NULL;
+ pmchild->bgworker_notify = false;
+
+ dlist_push_head(&ActiveChildList, &pmchild->elem);
+ }
+
+ return pmchild;
+}
+
+/*
+ * Release a PMChild slot, after the child process has exited.
+ *
+ * Returns true if the child detached cleanly from shared memory, false
+ * otherwise (see MarkPostmasterChildSlotUnassigned).
+ */
+bool
+ReleasePostmasterChildSlot(PMChild *pmchild)
+{
+ dlist_delete(&pmchild->elem);
+ if (pmchild->bkend_type == B_DEAD_END_BACKEND)
+ {
+ elog(DEBUG2, "releasing dead-end backend");
+ pfree(pmchild);
+ return true;
+ }
+ else
+ {
+ PMChildPool *pool;
+
+ elog(DEBUG2, "releasing pm child slot %d", pmchild->child_slot);
+
+ /* WAL senders start out as regular backends, and share the pool */
+ if (pmchild->bkend_type == B_WAL_SENDER)
+ pool = &pmchild_pools[B_BACKEND];
+ else
+ pool = &pmchild_pools[pmchild->bkend_type];
+
+ /* sanity check that we return the entry to the right pool */
+ if (!(pmchild->child_slot >= pool->first_slotno &&
+ pmchild->child_slot < pool->first_slotno + pool->size))
+ {
+ elog(ERROR, "pmchild freelist for backend type %d is corrupt",
+ pmchild->bkend_type);
+ }
+
+ dlist_push_head(&pool->freelist, &pmchild->elem);
+ return MarkPostmasterChildSlotUnassigned(pmchild->child_slot);
+ }
+}
+
+/*
+ * Find the PMChild entry of a running child process by PID.
+ */
+PMChild *
+FindPostmasterChildByPid(int pid)
+{
+ dlist_iter iter;
+
+ dlist_foreach(iter, &ActiveChildList)
+ {
+ PMChild *bp = dlist_container(PMChild, elem, iter.cur);
+
+ if (bp->pid == pid)
+ return bp;
+ }
+ return NULL;
+}