From a2f2e33805442597bb4c54ec858d0469835aaa9a Mon Sep 17 00:00:00 2001 From: heikki Date: Tue, 9 Dec 2008 14:28:20 +0000 Subject: [PATCH] Provide support for multiplexing SIGUSR1 signal. The upcoming synchronous replication patch needs a signal, but we've already used SIGUSR1 and SIGUSR2 in normal backends. This patch allows reusing SIGUSR1 for that, and for other purposes too if the need arises. --- src/backend/access/transam/twophase.c | 1 + src/backend/commands/async.c | 9 ++++--- src/backend/postmaster/autovacuum.c | 2 +- src/backend/storage/ipc/sinval.c | 21 +++++++-------- src/backend/storage/ipc/sinvaladt.c | 35 +++++++++++++----------- src/backend/storage/lmgr/proc.c | 50 +++++++++++++++++++++++++++++++++++ src/backend/tcop/postgres.c | 19 ++++++++++++- src/include/storage/proc.h | 28 ++++++++++++++++++++ src/include/storage/sinval.h | 2 +- src/include/tcop/tcopprot.h | 1 + 10 files changed, 133 insertions(+), 35 deletions(-) diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 8a6b8bdfd1..9154253a2f 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -287,6 +287,7 @@ MarkAsPreparing(TransactionId xid, const char *gid, gxact->proc.databaseId = databaseid; gxact->proc.roleId = owner; gxact->proc.inCommit = false; + MemSet(gxact->proc.signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t)); gxact->proc.vacuumFlags = 0; gxact->proc.lwWaiting = false; gxact->proc.lwExclusive = false; diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 99da39f825..010a2a4291 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -915,9 +915,10 @@ EnableNotifyInterrupt(void) * a frontend command. Signal handler execution of inbound notifies * is disabled until the next EnableNotifyInterrupt call. * - * The SIGUSR1 signal handler also needs to call this, so as to - * prevent conflicts if one signal interrupts the other. So we - * must return the previous state of the flag. + * This also needs to be called when SIGUSR1 with + * PROCSIG_CATCHUP_INTERRUPT is received, so as to prevent conflicts + * if one signal interrupts the other. So we must return the previous + * state of the flag. */ bool DisableNotifyInterrupt(void) @@ -954,7 +955,7 @@ ProcessIncomingNotify(void) nulls[Natts_pg_listener]; bool catchup_enabled; - /* Must prevent SIGUSR1 interrupt while I am running */ + /* Must prevent catchup interrupt while I am running */ catchup_enabled = DisableCatchupInterrupt(); if (Trace_notify) diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 2c68779b23..fc1fac80e8 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -1477,7 +1477,7 @@ AutoVacWorkerMain(int argc, char *argv[]) pqsignal(SIGALRM, handle_sig_alarm); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, CatchupInterruptHandler); + pqsignal(SIGUSR1, proc_sigusr1_handler); /* We don't listen for async notifies */ pqsignal(SIGUSR2, SIG_IGN); pqsignal(SIGFPE, FloatExceptionHandler); diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c index 06da9254cf..45eef7f664 100644 --- a/src/backend/storage/ipc/sinval.c +++ b/src/backend/storage/ipc/sinval.c @@ -26,8 +26,8 @@ * Because backends sitting idle will not be reading sinval events, we * need a way to give an idle backend a swift kick in the rear and make * it catch up before the sinval queue overflows and forces it to go - * through a cache reset exercise. This is done by sending SIGUSR1 - * to any backend that gets too far behind. + * through a cache reset exercise. This is done by sending + * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind. * * State for catchup events consists of two flags: one saying whether * the signal handler is currently allowed to call ProcessCatchupEvent @@ -144,9 +144,9 @@ ReceiveSharedInvalidMessages( /* - * CatchupInterruptHandler + * HandleCatchupInterrupt * - * This is the signal handler for SIGUSR1. + * This is called when PROCSIG_CATCHUP_INTERRUPT signal is received. * * If we are idle (catchupInterruptEnabled is set), we can safely * invoke ProcessCatchupEvent directly. Otherwise, just set a flag @@ -156,13 +156,11 @@ ReceiveSharedInvalidMessages( * since there's no longer any reason to do anything.) */ void -CatchupInterruptHandler(SIGNAL_ARGS) +HandleCatchupInterrupt(void) { - int save_errno = errno; - /* - * Note: this is a SIGNAL HANDLER. You must be very wary what you do - * here. + * Note: this is called by a SIGNAL HANDLER. + * You must be very wary what you do here. */ /* Don't joggle the elbow of proc_exit */ @@ -216,8 +214,6 @@ CatchupInterruptHandler(SIGNAL_ARGS) */ catchupInterruptOccurred = 1; } - - errno = save_errno; } /* @@ -289,7 +285,8 @@ DisableCatchupInterrupt(void) /* * ProcessCatchupEvent * - * Respond to a catchup event (SIGUSR1) from another backend. + * Respond to a catchup event (PROCSIG_CATCHUP_INTERRUPT) from another + * backend. * * This is called either directly from the SIGUSR1 signal handler, * or the next time control reaches the outer idle loop (assuming diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 98ae9a3dda..fc22c550be 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -21,6 +21,7 @@ #include "storage/backendid.h" #include "storage/ipc.h" #include "storage/proc.h" +#include "storage/procarray.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" #include "storage/spin.h" @@ -136,9 +137,9 @@ /* Per-backend state in shared invalidation structure */ typedef struct ProcState { - /* procPid is zero in an inactive ProcState array entry. */ - pid_t procPid; /* PID of backend, for signaling */ - /* nextMsgNum is meaningless if procPid == 0 or resetState is true. */ + /* proc is NULL in an inactive ProcState array entry. */ + PGPROC *proc; /* PGPROC entry of backend, for signaling */ + /* nextMsgNum is meaningless if proc == NULL or resetState is true. */ int nextMsgNum; /* next message number to read */ bool resetState; /* backend needs to reset its state */ bool signaled; /* backend has been sent catchup signal */ @@ -235,7 +236,7 @@ CreateSharedInvalidationState(void) /* Mark all backends inactive, and initialize nextLXID */ for (i = 0; i < shmInvalBuffer->maxBackends; i++) { - shmInvalBuffer->procState[i].procPid = 0; /* inactive */ + shmInvalBuffer->procState[i].proc = NULL; /* inactive */ shmInvalBuffer->procState[i].nextMsgNum = 0; /* meaningless */ shmInvalBuffer->procState[i].resetState = false; shmInvalBuffer->procState[i].signaled = false; @@ -266,7 +267,7 @@ SharedInvalBackendInit(void) /* Look for a free entry in the procState array */ for (index = 0; index < segP->lastBackend; index++) { - if (segP->procState[index].procPid == 0) /* inactive slot? */ + if (segP->procState[index].proc == NULL) /* inactive slot? */ { stateP = &segP->procState[index]; break; @@ -278,7 +279,7 @@ SharedInvalBackendInit(void) if (segP->lastBackend < segP->maxBackends) { stateP = &segP->procState[segP->lastBackend]; - Assert(stateP->procPid == 0); + Assert(stateP->proc == NULL); segP->lastBackend++; } else @@ -303,7 +304,7 @@ SharedInvalBackendInit(void) nextLocalTransactionId = stateP->nextLXID; /* mark myself active, with all extant messages already read */ - stateP->procPid = MyProcPid; + stateP->proc = MyProc; stateP->nextMsgNum = segP->maxMsgNum; stateP->resetState = false; stateP->signaled = false; @@ -341,7 +342,7 @@ CleanupInvalidationState(int status, Datum arg) stateP->nextLXID = nextLocalTransactionId; /* Mark myself inactive */ - stateP->procPid = 0; + stateP->proc = NULL; stateP->nextMsgNum = 0; stateP->resetState = false; stateP->signaled = false; @@ -349,7 +350,7 @@ CleanupInvalidationState(int status, Datum arg) /* Recompute index of last active backend */ for (i = segP->lastBackend; i > 0; i--) { - if (segP->procState[i - 1].procPid != 0) + if (segP->procState[i - 1].proc != NULL) break; } segP->lastBackend = i; @@ -374,7 +375,7 @@ BackendIdIsActive(int backendID) { ProcState *stateP = &segP->procState[backendID - 1]; - result = (stateP->procPid != 0); + result = (stateP->proc != NULL); } else result = false; @@ -590,7 +591,7 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) int n = stateP->nextMsgNum; /* Ignore if inactive or already in reset state */ - if (stateP->procPid == 0 || stateP->resetState) + if (stateP->proc == NULL || stateP->resetState) continue; /* @@ -644,18 +645,20 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM; /* - * Lastly, signal anyone who needs a catchup interrupt. Since kill() - * might not be fast, we don't want to hold locks while executing it. + * Lastly, signal anyone who needs a catchup interrupt. Since + * SendProcSignal() might not be fast, we don't want to hold locks while + * executing it. */ if (needSig) { - pid_t his_pid = needSig->procPid; + PGPROC *his_proc = needSig->proc; needSig->signaled = true; LWLockRelease(SInvalReadLock); LWLockRelease(SInvalWriteLock); - elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid); - kill(his_pid, SIGUSR1); + elog(DEBUG4, "sending sinval catchup signal to PID %d", + (int) his_proc->pid); + SendProcSignal(his_proc, PROCSIG_CATCHUP_INTERRUPT); if (callerHasWriteLock) LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE); } diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index f1f4d79bab..d9da802f68 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -289,6 +289,7 @@ InitProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; + MemSet(MyProc->signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t)); MyProc->vacuumFlags = 0; if (IsAutoVacuumWorkerProcess()) MyProc->vacuumFlags |= PROC_IS_AUTOVACUUM; @@ -428,6 +429,7 @@ InitAuxiliaryProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->inCommit = false; + MemSet(MyProc->signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t)); /* we don't set the "is autovacuum" flag in the launcher */ MyProc->vacuumFlags = 0; MyProc->lwWaiting = false; @@ -1277,6 +1279,54 @@ ProcSendSignal(int pid) PGSemaphoreUnlock(&proc->sem); } +/* + * SendProcSignal - send the signal with the reason to a process. + * + * The process can be a backend or an auxiliary process that has a PGPROC + * entry, like an autovacuum worker. + */ +void +SendProcSignal(PGPROC *proc, ProcSignalReason reason) +{ + pid_t pid; + + /* + * If the process is gone, do nothing. + * + * Since there's no locking, it's possible that the process detaches + * from shared memory and exits right after this test, before we set + * the flag and send signal. And the PGPROC entry might even be recycled + * by a new process, so it's remotely possible that we signal a wrong + * process. That's OK, all the signals are such that no harm is done. + */ + pid = proc->pid; + if (pid == 0) + return; + + /* Atomically set the proper flag */ + proc->signalFlags[reason] = true; + /* Send SIGUSR1 to the process */ + kill(pid, SIGUSR1); +} + +/* + * CheckProcSignal - check to see if the particular reason has been + * signaled, and clear the signal flag. Should be called after + * receiving SIGUSR1. + */ +bool +CheckProcSignal(ProcSignalReason reason) +{ + /* Careful here --- don't clear flag if we haven't seen it set */ + if (MyProc->signalFlags[reason]) + { + MyProc->signalFlags[reason] = false; + return true; + } + + return false; +} + /***************************************************************************** * SIGALRM interrupt support diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 90c4c8400e..0049c86c13 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2437,6 +2437,23 @@ drop_unnamed_stmt(void) */ /* + * proc_sigusr1_handler - handle SIGUSR1 signal. + * + * SIGUSR1 is multiplexed to handle multiple different events. The signalFlags + * array in PGPROC indicates which events have been signaled. + */ +void +proc_sigusr1_handler(SIGNAL_ARGS) +{ + int save_errno = errno; + + if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT)) + HandleCatchupInterrupt(); + + errno = save_errno; +} + +/* * quickdie() occurs when signalled SIGQUIT by the postmaster. * * Some backend has bought the farm, @@ -3180,7 +3197,7 @@ PostgresMain(int argc, char *argv[], const char *username) * of output during who-knows-what operation... */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, CatchupInterruptHandler); + pqsignal(SIGUSR1, proc_sigusr1_handler); pqsignal(SIGUSR2, NotifyInterruptHandler); pqsignal(SIGFPE, FloatExceptionHandler); diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index de80967653..fc372958bc 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -14,6 +14,8 @@ #ifndef _PROC_H_ #define _PROC_H_ +#include + #include "storage/lock.h" #include "storage/pg_sema.h" @@ -38,6 +40,19 @@ struct XidCache TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS]; }; +/* + * Reasons for signaling a process (a backend or an auxiliary process, like + * autovacuum worker). We can cope with simultaneous signals for different + * reasons. If the same reason is signaled multiple times in quick succession, + * however, the process is likely to observe only one notification of it. + * This is okay for the present uses. + */ +typedef enum +{ + PROCSIG_CATCHUP_INTERRUPT, /* catchup interrupt */ + NUM_PROCSIGNALS /* Must be last value of enum! */ +} ProcSignalReason; + /* Flags for PGPROC->vacuumFlags */ #define PROC_IS_AUTOVACUUM 0x01 /* is it an autovac worker? */ #define PROC_IN_VACUUM 0x02 /* currently running lazy vacuum */ @@ -93,6 +108,16 @@ struct PGPROC uint8 vacuumFlags; /* vacuum-related flags, see above */ + /* + * SIGUSR1 signal is multiplexed for multiple purposes. signalFlags + * indicates which "reasons" have been signaled. + * + * The flags are 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. + */ + volatile sig_atomic_t signalFlags[NUM_PROCSIGNALS]; + /* Info about LWLock the process is currently waiting for, if any. */ bool lwWaiting; /* true if waiting for an LW lock */ bool lwExclusive; /* true if waiting for exclusive access */ @@ -171,6 +196,9 @@ extern void LockWaitCancel(void); extern void ProcWaitForSignal(void); extern void ProcSendSignal(int pid); +extern void SendProcSignal(PGPROC *proc, ProcSignalReason reason); +extern bool CheckProcSignal(ProcSignalReason reason); + extern bool enable_sig_alarm(int delayms, bool is_statement_timeout); extern bool disable_sig_alarm(bool is_statement_timeout); extern void handle_sig_alarm(SIGNAL_ARGS); diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h index dedf8a5bcb..ac94a36dbe 100644 --- a/src/include/storage/sinval.h +++ b/src/include/storage/sinval.h @@ -90,7 +90,7 @@ extern void ReceiveSharedInvalidMessages( void (*resetFunction) (void)); /* signal handler for catchup events (SIGUSR1) */ -extern void CatchupInterruptHandler(SIGNAL_ARGS); +extern void HandleCatchupInterrupt(void); /* * enable/disable processing of catchup events directly from signal handler. diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index 9f41822755..061c571ab8 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -56,6 +56,7 @@ extern List *pg_plan_queries(List *querytrees, int cursorOptions, extern bool assign_max_stack_depth(int newval, bool doit, GucSource source); +extern void proc_sigusr1_handler(SIGNAL_ARGS); extern void die(SIGNAL_ARGS); extern void quickdie(SIGNAL_ARGS); extern void authdie(SIGNAL_ARGS); -- 2.11.4.GIT