diff options
Diffstat (limited to 'src/backend/storage/ipc/pmsignal.c')
-rw-r--r-- | src/backend/storage/ipc/pmsignal.c | 462 |
1 files changed, 462 insertions, 0 deletions
diff --git a/src/backend/storage/ipc/pmsignal.c b/src/backend/storage/ipc/pmsignal.c new file mode 100644 index 0000000..5dc2da6 --- /dev/null +++ b/src/backend/storage/ipc/pmsignal.c @@ -0,0 +1,462 @@ +/*------------------------------------------------------------------------- + * + * pmsignal.c + * routines for signaling between the postmaster and its child processes + * + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/storage/ipc/pmsignal.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include <signal.h> +#include <unistd.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include "miscadmin.h" +#include "postmaster/postmaster.h" +#include "replication/walsender.h" +#include "storage/pmsignal.h" +#include "storage/shmem.h" +#include "utils/memutils.h" + + +/* + * The postmaster is signaled by its children by sending SIGUSR1. The + * specific reason is communicated via flags in shared memory. We keep + * a boolean flag for each possible "reason", so that different reasons + * can be signaled by different backends at the same time. (However, + * if the same reason is signaled more than once simultaneously, the + * postmaster will observe it only once.) + * + * The flags are actually declared as "volatile sig_atomic_t" for maximum + * portability. This should ensure that loads and stores of the flag + * values are atomic, allowing us to dispense with any explicit locking. + * + * In addition to the per-reason flags, we store a set of per-child-process + * flags that are currently used only for detecting whether a backend has + * exited without performing proper shutdown. The per-child-process flags + * have three possible states: UNUSED, ASSIGNED, ACTIVE. An UNUSED slot is + * available for assignment. An ASSIGNED slot is associated with a postmaster + * child process, but either the process has not touched shared memory yet, + * or it has successfully cleaned up after itself. A ACTIVE slot means the + * process is actively using shared memory. The slots are assigned to + * child processes at random, and postmaster.c is responsible for tracking + * which one goes with which PID. + * + * Actually there is a fourth state, WALSENDER. This is just like ACTIVE, + * but carries the extra information that the child is a WAL sender. + * WAL senders too start in ACTIVE state, but switch to WALSENDER once they + * start streaming the WAL (and they never go back to ACTIVE after that). + * + * We also have a shared-memory field that is used for communication in + * the opposite direction, from postmaster to children: it tells why the + * postmaster has broadcasted SIGQUIT signals, if indeed it has done so. + */ + +#define PM_CHILD_UNUSED 0 /* these values must fit in sig_atomic_t */ +#define PM_CHILD_ASSIGNED 1 +#define PM_CHILD_ACTIVE 2 +#define PM_CHILD_WALSENDER 3 + +/* "typedef struct PMSignalData PMSignalData" appears in pmsignal.h */ +struct PMSignalData +{ + /* per-reason flags for signaling the postmaster */ + sig_atomic_t PMSignalFlags[NUM_PMSIGNALS]; + /* global flags for signals from postmaster to children */ + QuitSignalReason sigquit_reason; /* why SIGQUIT was sent */ + /* per-child-process flags */ + int num_child_flags; /* # of entries in PMChildFlags[] */ + sig_atomic_t PMChildFlags[FLEXIBLE_ARRAY_MEMBER]; +}; + +/* PMSignalState pointer is valid in both postmaster and child processes */ +NON_EXEC_STATIC volatile PMSignalData *PMSignalState = NULL; + +/* + * These static variables are valid only in the postmaster. We keep a + * duplicative private array so that we can trust its state even if some + * failing child has clobbered the PMSignalData struct in shared memory. + */ +static int num_child_inuse; /* # of entries in PMChildInUse[] */ +static int next_child_inuse; /* next slot to try to assign */ +static bool *PMChildInUse; /* true if i'th flag slot is assigned */ + +/* + * Signal handler to be notified if postmaster dies. + */ +#ifdef USE_POSTMASTER_DEATH_SIGNAL +volatile sig_atomic_t postmaster_possibly_dead = false; + +static void +postmaster_death_handler(SIGNAL_ARGS) +{ + postmaster_possibly_dead = true; +} + +/* + * The available signals depend on the OS. SIGUSR1 and SIGUSR2 are already + * used for other things, so choose another one. + * + * Currently, we assume that we can always find a signal to use. That + * seems like a reasonable assumption for all platforms that are modern + * enough to have a parent-death signaling mechanism. + */ +#if defined(SIGINFO) +#define POSTMASTER_DEATH_SIGNAL SIGINFO +#elif defined(SIGPWR) +#define POSTMASTER_DEATH_SIGNAL SIGPWR +#else +#error "cannot find a signal to use for postmaster death" +#endif + +#endif /* USE_POSTMASTER_DEATH_SIGNAL */ + +/* + * PMSignalShmemSize + * Compute space needed for pmsignal.c's shared memory + */ +Size +PMSignalShmemSize(void) +{ + Size size; + + size = offsetof(PMSignalData, PMChildFlags); + size = add_size(size, mul_size(MaxLivePostmasterChildren(), + sizeof(sig_atomic_t))); + + return size; +} + +/* + * PMSignalShmemInit - initialize during shared-memory creation + */ +void +PMSignalShmemInit(void) +{ + bool found; + + PMSignalState = (PMSignalData *) + ShmemInitStruct("PMSignalState", PMSignalShmemSize(), &found); + + if (!found) + { + /* initialize all flags to zeroes */ + MemSet(unvolatize(PMSignalData *, PMSignalState), 0, PMSignalShmemSize()); + num_child_inuse = MaxLivePostmasterChildren(); + PMSignalState->num_child_flags = num_child_inuse; + + /* + * Also allocate postmaster's private PMChildInUse[] array. We + * might've already done that in a previous shared-memory creation + * cycle, in which case free the old array to avoid a leak. (Do it + * like this to support the possibility that MaxLivePostmasterChildren + * changed.) In a standalone backend, we do not need this. + */ + if (PostmasterContext != NULL) + { + if (PMChildInUse) + pfree(PMChildInUse); + PMChildInUse = (bool *) + MemoryContextAllocZero(PostmasterContext, + num_child_inuse * sizeof(bool)); + } + next_child_inuse = 0; + } +} + +/* + * SendPostmasterSignal - signal the postmaster from a child process + */ +void +SendPostmasterSignal(PMSignalReason reason) +{ + /* If called in a standalone backend, do nothing */ + if (!IsUnderPostmaster) + return; + /* Atomically set the proper flag */ + PMSignalState->PMSignalFlags[reason] = true; + /* Send signal to postmaster */ + kill(PostmasterPid, SIGUSR1); +} + +/* + * CheckPostmasterSignal - check to see if a particular reason has been + * signaled, and clear the signal flag. Should be called by postmaster + * after receiving SIGUSR1. + */ +bool +CheckPostmasterSignal(PMSignalReason reason) +{ + /* Careful here --- don't clear flag if we haven't seen it set */ + if (PMSignalState->PMSignalFlags[reason]) + { + PMSignalState->PMSignalFlags[reason] = false; + return true; + } + return false; +} + +/* + * SetQuitSignalReason - broadcast the reason for a system shutdown. + * Should be called by postmaster before sending SIGQUIT to children. + * + * Note: in a crash-and-restart scenario, the "reason" field gets cleared + * as a part of rebuilding shared memory; the postmaster need not do it + * explicitly. + */ +void +SetQuitSignalReason(QuitSignalReason reason) +{ + PMSignalState->sigquit_reason = reason; +} + +/* + * GetQuitSignalReason - obtain the reason for a system shutdown. + * Called by child processes when they receive SIGQUIT. + * If the postmaster hasn't actually sent SIGQUIT, will return PMQUIT_NOT_SENT. + */ +QuitSignalReason +GetQuitSignalReason(void) +{ + /* This is called in signal handlers, so be extra paranoid. */ + if (!IsUnderPostmaster || PMSignalState == NULL) + return PMQUIT_NOT_SENT; + return PMSignalState->sigquit_reason; +} + + +/* + * AssignPostmasterChildSlot - select an unused slot for a new postmaster + * child process, and set its state to ASSIGNED. Returns a slot number + * (one to N). + * + * Only the postmaster is allowed to execute this routine, so we need no + * special locking. + */ +int +AssignPostmasterChildSlot(void) +{ + int slot = next_child_inuse; + int n; + + /* + * Scan for a free slot. Notice that we trust nothing about the contents + * of PMSignalState, but use only postmaster-local data for this decision. + * We track the last slot assigned so as not to waste time repeatedly + * rescanning low-numbered slots. + */ + for (n = num_child_inuse; n > 0; n--) + { + if (--slot < 0) + slot = num_child_inuse - 1; + if (!PMChildInUse[slot]) + { + PMChildInUse[slot] = true; + PMSignalState->PMChildFlags[slot] = PM_CHILD_ASSIGNED; + next_child_inuse = slot; + return slot + 1; + } + } + + /* Out of slots ... should never happen, else postmaster.c messed up */ + elog(FATAL, "no free slots in PMChildFlags array"); + return 0; /* keep compiler quiet */ +} + +/* + * ReleasePostmasterChildSlot - release a slot after death of a postmaster + * child process. This must be called in the postmaster process. + * + * Returns true if the slot had been in ASSIGNED state (the expected case), + * false otherwise (implying that the child failed to clean itself up). + */ +bool +ReleasePostmasterChildSlot(int slot) +{ + bool result; + + Assert(slot > 0 && slot <= num_child_inuse); + slot--; + + /* + * Note: the slot state might already be unused, because the logic in + * postmaster.c is such that this might get called twice when a child + * crashes. So we don't try to Assert anything about the state. + */ + result = (PMSignalState->PMChildFlags[slot] == PM_CHILD_ASSIGNED); + PMSignalState->PMChildFlags[slot] = PM_CHILD_UNUSED; + PMChildInUse[slot] = false; + return result; +} + +/* + * IsPostmasterChildWalSender - check if given slot is in use by a + * walsender process. This is called only by the postmaster. + */ +bool +IsPostmasterChildWalSender(int slot) +{ + Assert(slot > 0 && slot <= num_child_inuse); + slot--; + + if (PMSignalState->PMChildFlags[slot] == PM_CHILD_WALSENDER) + return true; + else + return false; +} + +/* + * MarkPostmasterChildActive - mark a postmaster child as about to begin + * actively using shared memory. This is called in the child process. + */ +void +MarkPostmasterChildActive(void) +{ + int slot = MyPMChildSlot; + + Assert(slot > 0 && slot <= PMSignalState->num_child_flags); + slot--; + Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ASSIGNED); + PMSignalState->PMChildFlags[slot] = PM_CHILD_ACTIVE; +} + +/* + * MarkPostmasterChildWalSender - mark a postmaster child as a WAL sender + * process. This is called in the child process, sometime after marking the + * child as active. + */ +void +MarkPostmasterChildWalSender(void) +{ + int slot = MyPMChildSlot; + + Assert(am_walsender); + + Assert(slot > 0 && slot <= PMSignalState->num_child_flags); + slot--; + Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ACTIVE); + PMSignalState->PMChildFlags[slot] = PM_CHILD_WALSENDER; +} + +/* + * MarkPostmasterChildInactive - mark a postmaster child as done using + * shared memory. This is called in the child process. + */ +void +MarkPostmasterChildInactive(void) +{ + int slot = MyPMChildSlot; + + Assert(slot > 0 && slot <= PMSignalState->num_child_flags); + slot--; + Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ACTIVE || + PMSignalState->PMChildFlags[slot] == PM_CHILD_WALSENDER); + PMSignalState->PMChildFlags[slot] = PM_CHILD_ASSIGNED; +} + + +/* + * PostmasterIsAliveInternal - check whether postmaster process is still alive + * + * This is the slow path of PostmasterIsAlive(), where the caller has already + * checked 'postmaster_possibly_dead'. (On platforms that don't support + * a signal for parent death, PostmasterIsAlive() is just an alias for this.) + */ +bool +PostmasterIsAliveInternal(void) +{ +#ifdef USE_POSTMASTER_DEATH_SIGNAL + /* + * Reset the flag before checking, so that we don't miss a signal if + * postmaster dies right after the check. If postmaster was indeed dead, + * we'll re-arm it before returning to caller. + */ + postmaster_possibly_dead = false; +#endif + +#ifndef WIN32 + { + char c; + ssize_t rc; + + rc = read(postmaster_alive_fds[POSTMASTER_FD_WATCH], &c, 1); + + /* + * In the usual case, the postmaster is still alive, and there is no + * data in the pipe. + */ + if (rc < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) + return true; + else + { + /* + * Postmaster is dead, or something went wrong with the read() + * call. + */ + +#ifdef USE_POSTMASTER_DEATH_SIGNAL + postmaster_possibly_dead = true; +#endif + + if (rc < 0) + elog(FATAL, "read on postmaster death monitoring pipe failed: %m"); + else if (rc > 0) + elog(FATAL, "unexpected data in postmaster death monitoring pipe"); + + return false; + } + } + +#else /* WIN32 */ + if (WaitForSingleObject(PostmasterHandle, 0) == WAIT_TIMEOUT) + return true; + else + { +#ifdef USE_POSTMASTER_DEATH_SIGNAL + postmaster_possibly_dead = true; +#endif + return false; + } +#endif /* WIN32 */ +} + +/* + * PostmasterDeathSignalInit - request signal on postmaster death if possible + */ +void +PostmasterDeathSignalInit(void) +{ +#ifdef USE_POSTMASTER_DEATH_SIGNAL + int signum = POSTMASTER_DEATH_SIGNAL; + + /* Register our signal handler. */ + pqsignal(signum, postmaster_death_handler); + + /* Request a signal on parent exit. */ +#if defined(PR_SET_PDEATHSIG) + if (prctl(PR_SET_PDEATHSIG, signum) < 0) + elog(ERROR, "could not request parent death signal: %m"); +#elif defined(PROC_PDEATHSIG_CTL) + if (procctl(P_PID, 0, PROC_PDEATHSIG_CTL, &signum) < 0) + elog(ERROR, "could not request parent death signal: %m"); +#else +#error "USE_POSTMASTER_DEATH_SIGNAL set, but there is no mechanism to request the signal" +#endif + + /* + * Just in case the parent was gone already and we missed it, we'd better + * check the slow way on the first call. + */ + postmaster_possibly_dead = true; +#endif /* USE_POSTMASTER_DEATH_SIGNAL */ +} |