From e623492590052f16841ffa171383766eb157074b Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Thu, 6 Mar 2025 18:16:16 +0200 Subject: [PATCH v7 4/4] Replace ProcSignals and other ad hoc signals with interrupts This replaces the INTERRUPT_GENERAL, or setting the process's latch before that, with more fine-grained interrupts. All the ProcSignals are converted into interrupts, as well as things like timers and config-reload requests. Most callers of WaitInterrupt now use INTERRUPT_CFI_MASK(), which is a bitmask that includes all the interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current state. CHECK_FOR_INTERRUPTS() now clears the interrupts bits that it processes, the caller is no longer required to do ClearInterrupt (or ResetLatch) for those bits. If you wake up for other interrupts that are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear those. Now that there are separate bits for different things, it's possible to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD | INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or query cancellation, and not wake up on other events like a shutdown request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS() still processes those interrupts if you call it, and you would want to process all the standard interrupts promptly anyway. It might be useful to check for specific interrupts with InterruptPending(), without immediately processing them, however. spgdoinsert() does something like that. In this commit I didn't change its overall logic, but it probably could be made more fine-grained now. More importantly, you can now have additional interrupts that are processed less frequently and do not wake up the backend when it's not prepared to process them. For example, the SINVAL_CATCHUP interrupt is only processed when the backend is idle. Previously, a catchup request would wake up the process whether it was ready to handle it or not; now it only wakes up when the backend is idle. That's not too significant for catchup interrupts because they don't occur that often anyway, but it's still nice and could be more significant with other interrupts. There's still a general-purpose INTERRUPT_GENERAL interrupt that is multiplexed for many different purposes in different processes, for example to wake up the walwriter when it has work to do, and to wake up processes waiting on a condition variable. The common property of those uses is that there's some other flag or condition that is checked on each wakeup, the wakeup interrupt itself means merely that something interesting might've happened. TODO: * ShutdownRequestPending became INTERRUPT_SHUTDOWN_AUX and ProcDiePending became INTERRUPT_DIE, but do we really need two different interrupts for requesting a process to terminate? * There are only a few interrupts bits left. Consolidate the existing ones, switch to a 64-bit atomic, or mulitplex some of them? XXX: original text from Thomas's patch: "ProcSignals" were a two-step way for one backend to ask another backend to do something, by multiplexing SIGUSR1. Historically, real work was done in the SIGUSR1 handler, but commit 0da096d7 removed the last case of that. Now the handler just set "interrupt pending" flags, called SetLatch(MyLatch) to close races, and then did all the interesting work at the next CHECK_FOR_INTERRUPTS() call. This commit removes the ProcSignal step, replacing ProcSignals with Interrupts. Benefits of this refactoring include removing unnecessary concepts and terminology, avoiding the risk of people putting buggy code back into signal handlers, removing a whole lot of global variables, removing dependencies on PIDs and the process model, removing risks of sending a signal that has default action of termination to the wrong process, and taking a step towards being able remove the fake signal system from the Windows port. Notable changes: * all calls to SendProcSignal(pid, PROCSIG_XXX) become SendInterrupt(INTERRUPT_XXX, procno) * all XxxPending global variables are gone; they are represented as bits in MyProc->pending_interrupts * the SIGUSR1 handler is gone * places that know the PIDs of other backends must now use procnos instead The only code left in src/backend/storage/ipc/procsignal.c relates to ProcSignalBarriers. XXX Those should perhaps be renamed. SIGUSR1 can now be set to SIG_IGN in most backends. Some special cases remain, that were not using the general sigusr1_handler (now removed). Author: Thomas Munro Author: Heikki Linnakangas Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com --- contrib/pg_prewarm/autoprewarm.c | 25 +- contrib/postgres_fdw/connection.c | 3 +- doc/src/sgml/sources.sgml | 3 +- src/backend/access/heap/vacuumlazy.c | 3 +- src/backend/access/spgist/spgdoinsert.c | 9 +- src/backend/access/transam/README.parallel | 2 +- src/backend/access/transam/parallel.c | 50 +-- src/backend/access/transam/xlog.c | 3 +- src/backend/access/transam/xlogfuncs.c | 6 +- src/backend/access/transam/xlogrecovery.c | 31 +- src/backend/backup/basebackup_throttle.c | 12 +- src/backend/commands/async.c | 103 +---- src/backend/commands/dbcommands.c | 1 + src/backend/commands/tablespace.c | 1 + src/backend/commands/vacuum.c | 7 +- src/backend/executor/execAsync.c | 2 +- src/backend/executor/nodeGather.c | 2 +- src/backend/libpq/be-secure.c | 18 +- src/backend/libpq/pqcomm.c | 22 +- src/backend/libpq/pqmq.c | 22 +- src/backend/postmaster/autovacuum.c | 43 +- src/backend/postmaster/bgworker.c | 7 +- src/backend/postmaster/bgwriter.c | 15 +- src/backend/postmaster/checkpointer.c | 70 ++-- src/backend/postmaster/interrupt.c | 33 +- src/backend/postmaster/pgarch.c | 20 +- src/backend/postmaster/postmaster.c | 3 +- src/backend/postmaster/startup.c | 16 +- src/backend/postmaster/syslogger.c | 8 +- src/backend/postmaster/walsummarizer.c | 26 +- src/backend/postmaster/walwriter.c | 8 +- .../libpqwalreceiver/libpqwalreceiver.c | 12 +- .../replication/logical/applyparallelworker.c | 57 +-- src/backend/replication/logical/launcher.c | 42 +- src/backend/replication/logical/slotsync.c | 57 +-- src/backend/replication/logical/tablesync.c | 12 +- src/backend/replication/logical/worker.c | 19 +- src/backend/replication/slot.c | 89 ++-- src/backend/replication/slotfuncs.c | 18 +- src/backend/replication/syncrep.c | 13 +- src/backend/replication/walreceiver.c | 19 +- src/backend/replication/walsender.c | 78 ++-- src/backend/storage/buffer/bufmgr.c | 18 +- src/backend/storage/ipc/ipc.c | 6 +- src/backend/storage/ipc/procarray.c | 20 +- src/backend/storage/ipc/procsignal.c | 242 ++--------- src/backend/storage/ipc/shm_mq.c | 64 +-- src/backend/storage/ipc/signalfuncs.c | 2 +- src/backend/storage/ipc/sinval.c | 52 +-- src/backend/storage/ipc/sinvaladt.c | 14 +- src/backend/storage/ipc/standby.c | 81 ++-- src/backend/storage/ipc/waiteventset.c | 4 +- src/backend/storage/lmgr/condition_variable.c | 3 +- src/backend/storage/lmgr/predicate.c | 5 +- src/backend/storage/lmgr/proc.c | 15 +- src/backend/tcop/postgres.c | 349 +++++++--------- src/backend/utils/activity/pgstat_database.c | 20 +- src/backend/utils/adt/mcxtfuncs.c | 16 +- src/backend/utils/adt/misc.c | 3 +- src/backend/utils/init/globals.c | 15 - src/backend/utils/init/postinit.c | 20 +- src/backend/utils/misc/timeout.c | 6 - src/backend/utils/mmgr/mcxt.c | 20 +- src/include/access/parallel.h | 4 - src/include/commands/async.h | 6 - src/include/libpq/libpq-be-fe-helpers.h | 14 +- src/include/libpq/pqmq.h | 2 +- src/include/miscadmin.h | 122 +----- src/include/pgstat.h | 2 +- src/include/postmaster/interrupt.h | 386 +++++++++++++++--- src/include/postmaster/startup.h | 8 + src/include/replication/logicalworker.h | 5 - src/include/replication/slot.h | 6 +- src/include/replication/walsender.h | 2 +- src/include/replication/walsender_private.h | 1 + src/include/replication/worker_internal.h | 5 +- src/include/storage/procarray.h | 7 +- src/include/storage/procsignal.h | 41 -- src/include/storage/sinval.h | 5 - src/include/storage/standby.h | 4 +- src/include/storage/waiteventset.h | 5 +- src/include/tcop/tcopprot.h | 6 +- src/include/utils/memutils.h | 1 - src/test/modules/test_shm_mq/setup.c | 9 +- src/test/modules/test_shm_mq/test.c | 6 +- src/test/modules/worker_spi/worker_spi.c | 9 +- 86 files changed, 1172 insertions(+), 1449 deletions(-) diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c index a52058be9bb..af00cfd9a05 100644 --- a/contrib/pg_prewarm/autoprewarm.c +++ b/contrib/pg_prewarm/autoprewarm.c @@ -39,7 +39,6 @@ #include "storage/fd.h" #include "storage/ipc.h" #include "storage/lwlock.h" -#include "storage/procsignal.h" #include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/guc.h" @@ -149,7 +148,7 @@ autoprewarm_main(Datum main_arg) /* Establish signal handlers; once that's done, unblock signals. */ pqsignal(SIGTERM, SignalHandlerForShutdownRequest); pqsignal(SIGHUP, SignalHandlerForConfigReload); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); BackgroundWorkerUnblockSignals(); /* Create (if necessary) and attach to our shared memory area. */ @@ -198,24 +197,22 @@ autoprewarm_main(Datum main_arg) if (first_time) { apw_load_buffers(); - final_dump_allowed = !ShutdownRequestPending; + final_dump_allowed = !IsInterruptPending(INTERRUPT_SHUTDOWN_AUX); last_dump_time = GetCurrentTimestamp(); } /* Periodically dump buffers until terminated. */ - while (!ShutdownRequestPending) + while (!IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) { /* In case of a SIGHUP, just reload the configuration. */ - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } if (autoprewarm_interval <= 0) { /* We're only dumping at shutdown, so just wait forever. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1L, PG_WAIT_EXTENSION); @@ -242,14 +239,12 @@ autoprewarm_main(Datum main_arg) } /* Sleep until the next dump time. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, delay_in_ms, PG_WAIT_EXTENSION); } - - /* Reset the latch, loop. */ - ClearInterrupt(INTERRUPT_GENERAL); } /* @@ -394,7 +389,7 @@ apw_load_buffers(void) * Likewise, don't launch if we've already been told to shut down. * (The launch would fail anyway, but we might as well skip it.) */ - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) break; /* @@ -415,7 +410,7 @@ apw_load_buffers(void) LWLockRelease(&apw_state->lock); /* Report our success, if we were able to finish. */ - if (!ShutdownRequestPending) + if (!IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) ereport(LOG, (errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks", apw_state->prewarmed_blocks, num_elements))); diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index 0c9fdeb92fd..88f08ac2465 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -1654,12 +1654,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime, pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult"); /* Sleep until there's something to do */ - wc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + wc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_SOCKET_READABLE | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, PQsocket(conn), cur_timeout, pgfdw_we_cleanup_result); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml index fc642f5f713..018d6970c24 100644 --- a/doc/src/sgml/sources.sgml +++ b/doc/src/sgml/sources.sgml @@ -1000,8 +1000,7 @@ MemoryContextSwitchTo(MemoryContext context) static void handle_sighup(SIGNAL_ARGS) { - got_SIGHUP = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_CONFIG_RELOAD); } diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 45f25270dd8..5890dc31820 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -3243,11 +3243,10 @@ lazy_truncate_heap(LVRelState *vacrel) return; } - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL, WAIT_EVENT_VACUUM_TRUNCATE); - ClearInterrupt(INTERRUPT_GENERAL); } /* diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c index af6b27b2135..fccd2fffdc8 100644 --- a/src/backend/access/spgist/spgdoinsert.c +++ b/src/backend/access/spgist/spgdoinsert.c @@ -2040,8 +2040,11 @@ spgdoinsert(Relation index, SpGistState *state, * pending, break out of the loop and deal with the situation below. * Set result = false because we must restart the insertion if the * interrupt isn't a query-cancel-or-die case. + * + * XXX: INTERRUPT_CFI_ALL_MASK covers more than just query cancel and die. + * Could we be more precise here? */ - if (INTERRUPTS_PENDING_CONDITION()) + if (IsInterruptPending(INTERRUPT_CFI_ALL_MASK)) { result = false; break; @@ -2167,7 +2170,7 @@ spgdoinsert(Relation index, SpGistState *state, * repeatedly, check for query cancel (see comments above). */ process_inner_tuple: - if (INTERRUPTS_PENDING_CONDITION()) + if (IsInterruptPending(INTERRUPT_CFI_ALL_MASK)) { result = false; break; @@ -2343,7 +2346,7 @@ spgdoinsert(Relation index, SpGistState *state, * were the case, telling the caller to retry would create an infinite * loop. */ - Assert(INTERRUPTS_CAN_BE_PROCESSED()); + Assert(INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_CFI_ALL_MASK)); /* * Finally, check for interrupts again. If there was a query cancel, diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel index 9df3da91b0c..a3593dd4f54 100644 --- a/src/backend/access/transam/README.parallel +++ b/src/backend/access/transam/README.parallel @@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to this case imposes no additional burden. Whenever a new message (or partial message; very large messages may wrap) is -sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the +sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the initiating backend. This causes the next CHECK_FOR_INTERRUPTS() in the initiating backend to read and rethrow the message. For the most part, this makes error reporting in parallel mode "just work". Of course, to work diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c index c4a1f69c619..25322a99522 100644 --- a/src/backend/access/transam/parallel.c +++ b/src/backend/access/transam/parallel.c @@ -115,9 +115,6 @@ typedef struct FixedParallelState */ int ParallelWorkerNumber = -1; -/* Is there a parallel message pending which we need to receive? */ -volatile sig_atomic_t ParallelMessagePending = false; - /* Are we initializing a parallel worker? */ bool InitializingParallelWorker = false; @@ -127,9 +124,6 @@ static FixedParallelState *MyFixedParallelState; /* List of active parallel contexts. */ static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list); -/* Backend-local copy of data from FixedParallelState. */ -static pid_t ParallelLeaderPid; - /* * List of internal parallel worker entry points. We need this for * reasons explained in LookupParallelWorkerFunction(), below. @@ -242,8 +236,10 @@ InitializeParallelDSM(ParallelContext *pcxt) * launch any workers: we would fail to process interrupts sent by them. * We can deal with that edge case by pretending no workers were * requested. + * + * XXX: could this be relaxed now that we have different interrupts bits? */ - if (!INTERRUPTS_CAN_BE_PROCESSED()) + if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_CFI_ALL_MASK)) pcxt->nworkers = 0; /* @@ -759,16 +755,20 @@ WaitForParallelWorkersToAttach(ParallelContext *pcxt) { /* * Worker not yet started, so we must wait. The postmaster - * will notify us if the worker's state changes. The - * interrupt might also get set for some other reason, but if - * so we'll just end up waiting for the same worker again. + * will notify us with INTERRUPT_GENERAL if the worker's state + * changes. The interrupt might also get set for some other + * reason, but if so we'll just end up waiting for the same + * worker again. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1, WAIT_EVENT_BGWORKER_STARTUP); if (rc & WL_INTERRUPT) + { + CHECK_FOR_INTERRUPTS(); ClearInterrupt(INTERRUPT_GENERAL); + } } } @@ -883,7 +883,7 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt) } } - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1, WAIT_EVENT_PARALLEL_FINISH); ClearInterrupt(INTERRUPT_GENERAL); @@ -1027,21 +1027,6 @@ ParallelContextActive(void) return !dlist_is_empty(&pcxt_list); } -/* - * Handle receipt of an interrupt indicating a parallel worker message. - * - * Note: this is called within a signal handler! All we can do is set - * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke - * ProcessParallelMessages(). - */ -void -HandleParallelMessageInterrupt(void) -{ - InterruptPending = true; - ParallelMessagePending = true; - RaiseInterrupt(INTERRUPT_GENERAL); -} - /* * Process any queued protocol messages received from parallel workers. */ @@ -1077,7 +1062,7 @@ ProcessParallelMessages(void) oldcontext = MemoryContextSwitchTo(hpm_context); /* OK to process messages. Reset the flag saying there are more to do. */ - ParallelMessagePending = false; + ClearInterrupt(INTERRUPT_PARALLEL_MESSAGE); dlist_foreach(iter, &pcxt_list) { @@ -1358,7 +1343,6 @@ ParallelWorkerMain(Datum main_arg) MyFixedParallelState = fps; /* Arrange to signal the leader if we exit. */ - ParallelLeaderPid = fps->parallel_leader_pid; ParallelLeaderProcNumber = fps->parallel_leader_proc_number; before_shmem_exit(ParallelWorkerShutdown, PointerGetDatum(seg)); @@ -1374,8 +1358,7 @@ ParallelWorkerMain(Datum main_arg) shm_mq_set_sender(mq, MyProc); mqh = shm_mq_attach(mq, seg, NULL); pq_redirect_to_shm_mq(seg, mqh); - pq_set_parallel_leader(fps->parallel_leader_pid, - fps->parallel_leader_proc_number); + pq_set_parallel_leader(fps->parallel_leader_proc_number); /* * Hooray! Primary initialization is complete. Now, we need to set up our @@ -1614,9 +1597,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end) static void ParallelWorkerShutdown(int code, Datum arg) { - SendProcSignal(ParallelLeaderPid, - PROCSIG_PARALLEL_MESSAGE, - ParallelLeaderProcNumber); + SendInterrupt(INTERRUPT_PARALLEL_MESSAGE, + ParallelLeaderProcNumber); dsm_detach((dsm_segment *) DatumGetPointer(arg)); } diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 940554def1a..df0dc61772f 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -9382,11 +9382,10 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive) reported_waiting = true; } - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 1000L, WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE); - ClearInterrupt(INTERRUPT_GENERAL); if (++waits >= seconds_before_warning) { diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c index 8f43ce62909..91700ca4b41 100644 --- a/src/backend/access/transam/xlogfuncs.c +++ b/src/backend/access/transam/xlogfuncs.c @@ -708,6 +708,8 @@ pg_promote(PG_FUNCTION_ARGS) errmsg("failed to send signal to postmaster: %m"))); } + /* XXX: we could interrupt the startup process directly */ + /* return immediately if waiting was not requested */ if (!wait) PG_RETURN_BOOL(true); @@ -718,14 +720,12 @@ pg_promote(PG_FUNCTION_ARGS) { int rc; - ClearInterrupt(INTERRUPT_GENERAL); - if (!RecoveryInProgress()) PG_RETURN_BOOL(true); CHECK_FOR_INTERRUPTS(); - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH, 1000L / WAITS_PER_SECOND, WAIT_EVENT_PROMOTE); diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c index c244f6c5ec0..4837739a868 100644 --- a/src/backend/access/transam/xlogrecovery.c +++ b/src/backend/access/transam/xlogrecovery.c @@ -3004,18 +3004,12 @@ recoveryApplyDelay(XLogReaderState *record) while (true) { - /* - * INTERRUPT_GENERAL is used for all the usual interrupts, like config - * reloads. The wakeups when more WAL arrive use a different - * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL - * arriving don't wake up the startup process excessively when we're - * waiting in other places, like for recovery conflicts. - */ - ClearInterrupt(INTERRUPT_GENERAL); - /* This might change recovery_min_apply_delay. */ ProcessStartupProcInterrupts(); + /* + * INTERRUPT_RECOVERY_CONTINUE indicates that more WAL has arrived. + */ ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE); if (CheckForStandbyTrigger()) break; @@ -3037,8 +3031,8 @@ recoveryApplyDelay(XLogReaderState *record) elog(DEBUG2, "recovery apply delay %ld milliseconds", msecs); - (void) WaitInterrupt(INTERRUPT_RECOVERY_CONTINUE | - INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK | + INTERRUPT_RECOVERY_CONTINUE, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, msecs, WAIT_EVENT_RECOVERY_APPLY_DELAY); @@ -3696,18 +3690,18 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, /* Do background tasks that might benefit us later. */ KnownAssignedTransactionIdsIdleMaintenance(); - (void) WaitInterrupt(INTERRUPT_RECOVERY_CONTINUE | - INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK | + INTERRUPT_RECOVERY_CONTINUE, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, wait_time, WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL); - ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE); now = GetCurrentTimestamp(); - /* Handle interrupt signals of startup process */ - ClearInterrupt(INTERRUPT_GENERAL); + /* Handle standard interrupts of startup process */ ProcessStartupProcInterrupts(); + + ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE); } last_fail_time = now; currentSource = XLOG_FROM_ARCHIVE; @@ -3973,8 +3967,8 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * Wait for more WAL to arrive, when we will be woken * immediately by the WAL receiver. */ - (void) WaitInterrupt(INTERRUPT_RECOVERY_CONTINUE | - INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK | + INTERRUPT_RECOVERY_CONTINUE, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1L, WAIT_EVENT_RECOVERY_WAL_STREAM); @@ -3998,7 +3992,6 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess, * This possibly-long loop needs to handle interrupts of startup * process. */ - ClearInterrupt(INTERRUPT_GENERAL); ProcessStartupProcInterrupts(); } diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c index 861043c6b4a..02ce65da3b6 100644 --- a/src/backend/backup/basebackup_throttle.c +++ b/src/backend/backup/basebackup_throttle.c @@ -155,6 +155,8 @@ throttle(bbsink_throttle *sink, size_t increment) sleep; int wait_result; + CHECK_FOR_INTERRUPTS(); + /* Time elapsed since the last measurement (and possible wake up). */ elapsed = GetCurrentTimestamp() - sink->throttled_last; @@ -163,23 +165,15 @@ throttle(bbsink_throttle *sink, size_t increment) if (sleep <= 0) break; - ClearInterrupt(INTERRUPT_GENERAL); - - /* We're eating a wakeup, so check for interrupts */ - CHECK_FOR_INTERRUPTS(); - /* * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be * the maximum time to sleep. Thus the cast to long is safe. */ - wait_result = WaitInterrupt(INTERRUPT_GENERAL, + wait_result = WaitInterrupt(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, (long) (sleep / 1000), WAIT_EVENT_BASE_BACKUP_THROTTLE); - if (wait_result & WL_INTERRUPT) - CHECK_FOR_INTERRUPTS(); - /* Done waiting? */ if (wait_result & WL_TIMEOUT) break; diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c index 2b5b92abe38..df4feee41a0 100644 --- a/src/backend/commands/async.c +++ b/src/backend/commands/async.c @@ -39,7 +39,7 @@ * is no need for WAL support or fsync'ing. * * 3. Every backend that is listening on at least one channel registers by - * entering its PID into the array in AsyncQueueControl. It then scans all + * entering itself into the array in AsyncQueueControl. It then scans all * incoming notifications in the central queue and first compares the * database OID of the notification with its own database OID and then * compares the notified channel with the list of channels that it listens @@ -72,7 +72,7 @@ * Then we signal any backends that may be interested in our messages * (including our own backend, if listening). This is done by * SignalBackends(), which scans the list of listening backends and sends a - * PROCSIG_NOTIFY_INTERRUPT signal to every listening backend (we don't + * INTERRUPT_ASYNC_NOTIFY interrupt to every listening backend (we don't * know which backend is listening on which channel so we must signal them * all). We can exclude backends that are already up to date, though, and * we can also exclude backends that are in other databases (unless they @@ -90,13 +90,11 @@ * frontend until the command is done; but notifies to other backends * should go out immediately after each commit. * - * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler - * raises INTERRUPT_GENERAL, which triggers the event to be processed - * immediately if this backend is idle (i.e., it is waiting for a frontend + * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the + * listening backend if it is idle (i.e., it is waiting for a frontend * command and is not within a transaction block. C.f. - * ProcessClientReadInterrupt()). Otherwise the handler may only set a - * flag, which will cause the processing to occur just before we next go - * idle. + * ProcessClientReadInterrupt()). Otherwise the interrupt is processed + * later, just before we next go idle. * * Inbound-notify processing consists of reading all of the notifications * that have arrived since scanning last time. We read every notification @@ -127,7 +125,6 @@ #include #include -#include #include "access/parallel.h" #include "access/slru.h" @@ -143,7 +140,6 @@ #include "postmaster/interrupt.h" #include "storage/ipc.h" #include "storage/lmgr.h" -#include "storage/procsignal.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" #include "utils/guc_hooks.h" @@ -271,7 +267,7 @@ typedef struct QueueBackendStatus * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock. * * Each backend uses the backend[] array entry with index equal to its - * ProcNumber. We rely on this to make SendProcSignal fast. + * ProcNumber. We rely on this for sending interrupts. * * The backend[] array entries for actively-listening backends are threaded * together using firstListener and the nextListener links, so that we can @@ -404,15 +400,6 @@ struct NotificationHash static NotificationList *pendingNotifies = NULL; -/* - * Inbound notifications are initially processed by HandleNotifyInterrupt(), - * called from inside a signal handler. That just sets the - * notifyInterruptPending flag and raises the INTERRUPT_GENERAL interrupt. - * ProcessNotifyInterrupt() will then be called whenever it's safe to actually - * deal with the interrupt. - */ -volatile sig_atomic_t notifyInterruptPending = false; - /* True if we've registered an on_shmem_exit cleanup */ static bool unlistenExitRegistered = false; @@ -1581,29 +1568,25 @@ asyncQueueFillWarning(void) static void SignalBackends(void) { - int32 *pids; ProcNumber *procnos; int count; /* * Identify backends that we need to signal. We don't want to send * signals while holding the NotifyQueueLock, so this loop just builds a - * list of target PIDs. + * list of target backends. * * XXX in principle these pallocs could fail, which would be bad. Maybe * preallocate the arrays? They're not that large, though. */ - pids = (int32 *) palloc(MaxBackends * sizeof(int32)); procnos = (ProcNumber *) palloc(MaxBackends * sizeof(ProcNumber)); count = 0; LWLockAcquire(NotifyQueueLock, LW_EXCLUSIVE); for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i)) { - int32 pid = QUEUE_BACKEND_PID(i); QueuePosition pos; - Assert(pid != InvalidPid); pos = QUEUE_BACKEND_POS(i); if (QUEUE_BACKEND_DBOID(i) == MyDatabaseId) { @@ -1625,38 +1608,27 @@ SignalBackends(void) continue; } /* OK, need to signal this one */ - pids[count] = pid; procnos[count] = i; count++; } LWLockRelease(NotifyQueueLock); - /* Now send signals */ + /* Now send the interrupts */ for (int i = 0; i < count; i++) { - int32 pid = pids[i]; + ProcNumber procno = procnos[i]; /* - * If we are signaling our own process, no need to involve the kernel; - * just set the flag directly. + * Note: it's unlikely but certainly possible that the backend exited + * since we released NotifyQueueLock. We'll send the interrupt to + * wrong backend in that case, but that's harmless. */ - if (pid == MyProcPid) - { - notifyInterruptPending = true; - continue; - } - - /* - * Note: assuming things aren't broken, a signal failure here could - * only occur if the target backend exited since we released - * NotifyQueueLock; which is unlikely but certainly possible. So we - * just log a low-level debug message if it happens. - */ - if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, procnos[i]) < 0) - elog(DEBUG3, "could not signal backend with PID %d: %m", pid); + if (procno == MyProcNumber) + RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY); + else + SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno); } - pfree(pids); pfree(procnos); } @@ -1793,39 +1765,12 @@ AtSubAbort_Notify(void) } } -/* - * HandleNotifyInterrupt - * - * Signal handler portion of interrupt handling. Let the backend know - * that there's a pending notify interrupt. If we're currently reading - * from the client, this will interrupt the read and - * ProcessClientReadInterrupt() will call ProcessNotifyInterrupt(). - */ -void -HandleNotifyInterrupt(void) -{ - /* - * Note: this is called by a SIGNAL HANDLER. You must be very wary what - * you do here. - */ - - /* signal that work needs to be done */ - notifyInterruptPending = true; - - /* make sure the event is processed in due course */ - RaiseInterrupt(INTERRUPT_GENERAL); -} - /* * ProcessNotifyInterrupt * - * This is called if we see notifyInterruptPending set, just before - * transmitting ReadyForQuery at the end of a frontend command, and - * also if a notify signal occurs while reading from the frontend. - * HandleNotifyInterrupt() will cause the read to be interrupted with - * INTERRUPT_GENERAL, and this routine will get called. If we are truly - * idle (ie, *not* inside a transaction block), process the incoming - * notifies. + * This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before + * transmitting ReadyForQuery at the end of a frontend command, and also + * if a notify signal occurs while reading from the frontend. * * If "flush" is true, force any frontend messages out immediately. * This can be false when being called at the end of a frontend command, @@ -1834,11 +1779,10 @@ HandleNotifyInterrupt(void) void ProcessNotifyInterrupt(bool flush) { - if (IsTransactionOrTransactionBlock()) - return; /* not really idle */ + Assert(!IsTransactionOrTransactionBlock()); /* Loop in case another signal arrives while sending messages */ - while (notifyInterruptPending) + while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY)) ProcessIncomingNotify(flush); } @@ -2183,9 +2127,6 @@ asyncQueueAdvanceTail(void) static void ProcessIncomingNotify(bool flush) { - /* We *must* reset the flag */ - notifyInterruptPending = false; - /* Do nothing else if we aren't actively listening */ if (listenChannels == NIL) return; diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index 1753b289074..d1557542b76 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -60,6 +60,7 @@ #include "storage/lmgr.h" #include "storage/md.h" #include "storage/procarray.h" +#include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c index a9005cc7212..80b6c721bac 100644 --- a/src/backend/commands/tablespace.c +++ b/src/backend/commands/tablespace.c @@ -70,6 +70,7 @@ #include "miscadmin.h" #include "postmaster/bgwriter.h" #include "storage/fd.h" +#include "storage/procsignal.h" #include "storage/standby.h" #include "utils/acl.h" #include "utils/builtins.h" diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 65b897476ce..6efcd247fe5 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2397,8 +2397,7 @@ vacuum_delay_point(bool is_analyze) /* Always check for interrupts */ CHECK_FOR_INTERRUPTS(); - if (InterruptPending || - (!VacuumCostActive && !ConfigReloadPending)) + if (!VacuumCostActive && !IsInterruptPending(INTERRUPT_CONFIG_RELOAD)) return; /* @@ -2407,9 +2406,9 @@ vacuum_delay_point(bool is_analyze) * [autovacuum_]vacuum_cost_delay to take effect while a table is being * vacuumed or analyzed. */ - if (ConfigReloadPending && AmAutoVacuumWorkerProcess()) + if (IsInterruptPending(INTERRUPT_CONFIG_RELOAD) && AmAutoVacuumWorkerProcess()) { - ConfigReloadPending = false; + ClearInterrupt(INTERRUPT_CONFIG_RELOAD); ProcessConfigFile(PGC_SIGHUP); VacuumUpdateCosts(); } diff --git a/src/backend/executor/execAsync.c b/src/backend/executor/execAsync.c index 5d3cabe73e3..a9be6285cbc 100644 --- a/src/backend/executor/execAsync.c +++ b/src/backend/executor/execAsync.c @@ -56,7 +56,7 @@ ExecAsyncRequest(AsyncRequest *areq) * for which it wishes to wait. We expect the node-type specific callback to * make a single call of the following form: * - * AddWaitEventToSet(set, WL_SOCKET_READABLE, fd, NULL, areq); + * AddWaitEventToSet(set, WL_SOCKET_READABLE, fd, 0, areq); */ void ExecAsyncConfigureWait(AsyncRequest *areq) diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c index 11e5b3f083b..9702260bf46 100644 --- a/src/backend/executor/nodeGather.c +++ b/src/backend/executor/nodeGather.c @@ -383,7 +383,7 @@ gather_readnext(GatherState *gatherstate) return NULL; /* Nothing to do except wait for developments. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_EXECUTE_GATHER); ClearInterrupt(INTERRUPT_GENERAL); diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c index 765ca0f3166..205729f408d 100644 --- a/src/backend/libpq/be-secure.c +++ b/src/backend/libpq/be-secure.c @@ -179,10 +179,11 @@ ssize_t secure_read(Port *port, void *ptr, size_t len) { ssize_t n; + uint32 interruptMask; int waitfor; /* Deal with any already-pending interrupt condition. */ - ProcessClientReadInterrupt(false); + interruptMask = ProcessClientReadInterrupt(false); retry: #ifdef USE_SSL @@ -214,6 +215,7 @@ retry: Assert(waitfor); ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0); + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask); WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1, WAIT_EVENT_CLIENT_READ); @@ -243,8 +245,7 @@ retry: /* Handle interrupt. */ if (event.events & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); - ProcessClientReadInterrupt(true); + interruptMask = ProcessClientReadInterrupt(true); /* * We'll retry the read. Most likely it will return immediately @@ -259,7 +260,7 @@ retry: * Process interrupts that happened during a successful (or non-blocking, * or hard-failed) read. */ - ProcessClientReadInterrupt(false); + (void) ProcessClientReadInterrupt(false); return n; } @@ -306,10 +307,11 @@ ssize_t secure_write(Port *port, const void *ptr, size_t len) { ssize_t n; + uint32 interruptMask; int waitfor; /* Deal with any already-pending interrupt condition. */ - ProcessClientWriteInterrupt(false); + interruptMask = ProcessClientWriteInterrupt(false); retry: waitfor = 0; @@ -340,6 +342,7 @@ retry: Assert(waitfor); ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0); + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask); WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1, WAIT_EVENT_CLIENT_WRITE); @@ -353,8 +356,7 @@ retry: /* Handle interrupt. */ if (event.events & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); - ProcessClientWriteInterrupt(true); + interruptMask = ProcessClientWriteInterrupt(true); /* * We'll retry the write. Most likely it will return immediately @@ -369,7 +371,7 @@ retry: * Process interrupts that happened during a successful (or non-blocking, * or hard-failed) write. */ - ProcessClientWriteInterrupt(false); + (void) ProcessClientWriteInterrupt(false); return n; } diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index f8465cb2d7b..77fd228089b 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -305,11 +305,15 @@ pq_init(ClientSocket *client_sock) elog(FATAL, "fcntl(F_SETFD) failed on socket: %m"); #endif + /* + * Add initial events to the set. These are modified with the correct + * event and interrupt masks on each use. + */ FeBeWaitSet = CreateWaitEventSet(NULL, FeBeWaitSetNEvents); socket_pos = AddWaitEventToSet(FeBeWaitSet, WL_SOCKET_WRITEABLE, port->sock, 0, NULL); interrupt_pos = AddWaitEventToSet(FeBeWaitSet, WL_INTERRUPT, PGINVALID_SOCKET, - INTERRUPT_GENERAL, NULL); + INTERRUPT_CFI_MASK(), NULL); AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET, 0, NULL); @@ -1413,8 +1417,7 @@ internal_flush_buffer(const char *buf, size_t *start, size_t *end) * the connection. */ *start = *end = 0; - ClientConnectionLost = 1; - InterruptPending = 1; + RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST); return EOF; } @@ -2065,24 +2068,13 @@ pq_check_connection(void) * all FeBeWaitSet socket wait sites do the same. */ ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, WL_SOCKET_CLOSED, 0); + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, 0); -retry: rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0); for (int i = 0; i < rc; ++i) { if (events[i].events & WL_SOCKET_CLOSED) return false; - if (events[i].events & WL_INTERRUPT) - { - /* - * An interrupt event might be preventing other events from being - * reported. Reset it and poll again. No need to restore it - * because no code should expect INTERRUPT_GENERAL to survive - * across CHECK_FOR_INTERRUPTS(). - */ - ClearInterrupt(INTERRUPT_GENERAL); - goto retry; - } } return true; diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c index b6928536984..0bc349262fd 100644 --- a/src/backend/libpq/pqmq.c +++ b/src/backend/libpq/pqmq.c @@ -26,7 +26,6 @@ static shm_mq_handle *pq_mq_handle; static bool pq_mq_busy = false; -static pid_t pq_mq_parallel_leader_pid = 0; static ProcNumber pq_mq_parallel_leader_proc_number = INVALID_PROC_NUMBER; static void pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg); @@ -72,14 +71,13 @@ pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg) } /* - * Arrange to SendProcSignal() to the parallel leader each time we transmit - * message data via the shm_mq. + * Arrange to interrupt to the parallel leader each time we transmit message + * data via the shm_mq. */ void -pq_set_parallel_leader(pid_t pid, ProcNumber procNumber) +pq_set_parallel_leader(ProcNumber procNumber) { Assert(PqCommMethods == &PqCommMqMethods); - pq_mq_parallel_leader_pid = pid; pq_mq_parallel_leader_proc_number = procNumber; } @@ -164,25 +162,23 @@ mq_putmessage(char msgtype, const char *s, size_t len) */ result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true); - if (pq_mq_parallel_leader_pid != 0) + if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER) { if (IsLogicalParallelApplyWorker()) - SendProcSignal(pq_mq_parallel_leader_pid, - PROCSIG_PARALLEL_APPLY_MESSAGE, - pq_mq_parallel_leader_proc_number); + SendInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE, + pq_mq_parallel_leader_proc_number); else { Assert(IsParallelWorker()); - SendProcSignal(pq_mq_parallel_leader_pid, - PROCSIG_PARALLEL_MESSAGE, - pq_mq_parallel_leader_proc_number); + SendInterrupt(INTERRUPT_PARALLEL_MESSAGE, + pq_mq_parallel_leader_proc_number); } } if (result != SHM_MQ_WOULD_BLOCK) break; - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE); ClearInterrupt(INTERRUPT_GENERAL); diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index c45b332e822..7bc08df4802 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -400,7 +400,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, avl_sigusr2_handler); pqsignal(SIGFPE, FloatExceptionHandler); pqsignal(SIGCHLD, SIG_DFL); @@ -450,7 +450,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) /* Forget any pending QueryCancel or timeout request */ disable_all_timeouts(false); - QueryCancelPending = false; /* second to avoid race condition */ + ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* second to avoid race condition */ /* Report the error to the server log */ EmitErrorReport(); @@ -491,7 +491,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) RESUME_INTERRUPTS(); /* if in shutdown mode, no need for anything further; just go away */ - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) AutoVacLauncherShutdown(); /* @@ -550,7 +550,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) */ if (!AutoVacuumingActive()) { - if (!ShutdownRequestPending) + if (!IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) do_start_worker(); proc_exit(0); /* done */ } @@ -567,7 +567,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) rebuild_database_list(InvalidOid); /* loop until shutdown request */ - while (!ShutdownRequestPending) + while (!IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) { struct timeval nap; TimestampTz current_time = 0; @@ -586,15 +586,19 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len) * Wait until naptime expires or we get some type of signal (all the * signal handlers will wake us by calling RaiseInterrupt). */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_BARRIER | + INTERRUPT_LOG_MEMORY_CONTEXT | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L), WAIT_EVENT_AUTOVACUUM_MAIN); - ClearInterrupt(INTERRUPT_GENERAL); - ProcessAutoVacLauncherInterrupts(); + ClearInterrupt(INTERRUPT_GENERAL); + /* * a worker finished, or postmaster signaled failure to start a worker */ @@ -744,14 +748,13 @@ static void ProcessAutoVacLauncherInterrupts(void) { /* the normal shutdown case */ - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) AutoVacLauncherShutdown(); - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { int autovacuum_max_workers_prev = autovacuum_max_workers; - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* shutdown requested in config file? */ @@ -771,11 +774,11 @@ ProcessAutoVacLauncherInterrupts(void) } /* Process barrier events */ - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); /* Process sinval catchup interrupts that happened while sleeping */ @@ -1407,7 +1410,7 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len) InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); pqsignal(SIGFPE, FloatExceptionHandler); pqsignal(SIGCHLD, SIG_DFL); @@ -1432,7 +1435,7 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len) * (to wit, BlockSig) will be restored when longjmp'ing to here. Thus, * signals other than SIGQUIT will be blocked until we exit. It might * seem that this policy makes the HOLD_INTERRUPTS() call redundant, but - * it is not since InterruptPending might be set already. + * it is not since XXXX InterruptPending might be set already. */ if (sigsetjmp(local_sigjmp_buf, 1) != 0) { @@ -2277,9 +2280,8 @@ do_autovacuum(void) /* * Check for config changes before processing each collected table. */ - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* @@ -2437,7 +2439,7 @@ do_autovacuum(void) * current table (we're done with it, so it would make no sense to * cancel at this point.) */ - QueryCancelPending = false; + ClearInterrupt(INTERRUPT_QUERY_CANCEL); } PG_CATCH(); { @@ -2521,9 +2523,8 @@ deleted: * Check for config changes before acquiring lock for further jobs. */ CHECK_FOR_INTERRUPTS(); - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); VacuumUpdateCosts(); } @@ -2635,7 +2636,7 @@ perform_work_item(AutoVacuumWorkItem *workitem) * (we're done with it, so it would make no sense to cancel at this * point.) */ - QueryCancelPending = false; + ClearInterrupt(INTERRUPT_QUERY_CANCEL); } PG_CATCH(); { diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c index 4425e404a35..8c522adb07a 100644 --- a/src/backend/postmaster/bgworker.c +++ b/src/backend/postmaster/bgworker.c @@ -26,7 +26,6 @@ #include "storage/lwlock.h" #include "storage/pmsignal.h" #include "storage/proc.h" -#include "storage/procsignal.h" #include "storage/shmem.h" #include "tcop/tcopprot.h" #include "utils/ascii.h" @@ -751,7 +750,7 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len) * SIGINT is used to signal canceling the current action */ pqsignal(SIGINT, StatementCancelHandler); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGFPE, FloatExceptionHandler); /* XXX Any other handlers needed here? */ @@ -1233,7 +1232,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp) if (status != BGWH_NOT_YET_STARTED) break; - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_POSTMASTER_DEATH, 0, WAIT_EVENT_BGWORKER_STARTUP); @@ -1277,7 +1276,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle) if (status == BGWH_STOPPED) break; - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_POSTMASTER_DEATH, 0, WAIT_EVENT_BGWORKER_SHUTDOWN); diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c index ae7d6f66cc6..2ab3f5049be 100644 --- a/src/backend/postmaster/bgwriter.c +++ b/src/backend/postmaster/bgwriter.c @@ -44,7 +44,6 @@ #include "storage/fd.h" #include "storage/lwlock.h" #include "storage/proc.h" -#include "storage/procsignal.h" #include "storage/smgr.h" #include "storage/standby.h" #include "utils/memutils.h" @@ -105,7 +104,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); /* @@ -223,9 +222,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len) bool can_hibernate; int rc; - /* Clear any already-pending wakeups */ - ClearInterrupt(INTERRUPT_GENERAL); - + /* Process any pending standard interrupts */ ProcessMainLoopInterrupts(); /* @@ -302,7 +299,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len) * down with interrupt events that are likely to happen frequently * during normal operation. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN); @@ -326,10 +323,14 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len) */ if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate) { + /* Clear any already-pending wakeups */ + ClearInterrupt(INTERRUPT_GENERAL); + /* Ask for notification at next buffer allocation */ StrategyNotifyBgWriter(MyProcNumber); /* Sleep ... */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, BgWriterDelay * HIBERNATE_FACTOR, WAIT_EVENT_BGWRITER_HIBERNATE); diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 363a42018e0..a0c1de9491f 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -80,10 +80,11 @@ * The algorithm for backends is: * 1. Record current values of ckpt_failed and ckpt_started, and * set request flags, while holding ckpt_lck. - * 2. Send signal to request checkpoint. + * 2. Send INTERRUPT_GENERAL to wake up the checkpointer. * 3. Sleep until ckpt_started changes. Now you know a checkpoint has * begun since you started this algorithm (although *not* that it was - * specifically initiated by your signal), and that it is using your flags. + * specifically initiated by your interrupt), and that it is using your + * flags. * 4. Record new value of ckpt_started. * 5. Sleep until ckpt_done >= saved value of ckpt_started. (Use modulo * arithmetic here in case counters wrap around.) Now you know a @@ -145,7 +146,6 @@ double CheckPointCompletionTarget = 0.9; * Private state */ static bool ckpt_active = false; -static volatile sig_atomic_t ShutdownXLOGPending = false; /* these values are valid when ckpt_active is true: */ static pg_time_t ckpt_start_time; @@ -201,7 +201,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SignalHandlerForShutdownRequest); /* @@ -355,12 +355,13 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) ClearInterrupt(INTERRUPT_GENERAL); /* - * Process any requests or signals received recently. + * Process any requests or interrupts received recently. */ AbsorbSyncRequests(); ProcessCheckpointerInterrupts(); - if (ShutdownXLOGPending || ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_XLOG) || + IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) break; /* @@ -537,7 +538,8 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) * interrupt might have been reset (e.g. in CheckpointWriteDelay). */ ProcessCheckpointerInterrupts(); - if (ShutdownXLOGPending || ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_XLOG) || + IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) break; } @@ -556,7 +558,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) continue; /* - * Sleep until we are signaled or it's time for another checkpoint or + * Sleep until we are woken up or it's time for another checkpoint or * xlog file switch. */ now = (pg_time_t) time(NULL); @@ -572,10 +574,18 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs); } - (void) WaitInterrupt(INTERRUPT_GENERAL, - WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, - cur_timeout * 1000L /* convert to ms */ , - WAIT_EVENT_CHECKPOINTER_MAIN); + (void) WaitInterrupt( + /* these are handled in the main loop */ + INTERRUPT_GENERAL | /* checkpoint requested */ + INTERRUPT_SHUTDOWN_XLOG | + INTERRUPT_SHUTDOWN_AUX | + /* ProcessCheckpointerInterrupts() */ + INTERRUPT_BARRIER | + INTERRUPT_LOG_MEMORY_CONTEXT | + INTERRUPT_CONFIG_RELOAD, + WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, + cur_timeout * 1000L /* convert to ms */ , + WAIT_EVENT_CHECKPOINTER_MAIN); } /* @@ -584,7 +594,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) */ ExitOnAnyError = true; - if (ShutdownXLOGPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_XLOG)) { /* * Close down the database. @@ -602,7 +612,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) * Tell postmaster that we're done. */ SendPostmasterSignal(PMSIGNAL_XLOG_IS_SHUTDOWN); - ShutdownXLOGPending = false; + ClearInterrupt(INTERRUPT_SHUTDOWN_XLOG); } /* @@ -612,15 +622,16 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) */ for (;;) { - /* Clear any already-pending wakeups */ - ClearInterrupt(INTERRUPT_GENERAL); - ProcessCheckpointerInterrupts(); - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) break; - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX | + /* ProcessCheckpointerInterrupts() */ + INTERRUPT_BARRIER | + INTERRUPT_LOG_MEMORY_CONTEXT | + INTERRUPT_CONFIG_RELOAD, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_CHECKPOINTER_SHUTDOWN); @@ -636,12 +647,11 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len) static void ProcessCheckpointerInterrupts(void) { - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* @@ -659,7 +669,7 @@ ProcessCheckpointerInterrupts(void) } /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); } @@ -777,14 +787,13 @@ CheckpointWriteDelay(int flags, double progress) * in which case we just try to catch up as quickly as possible. */ if (!(flags & CHECKPOINT_IMMEDIATE) && - !ShutdownXLOGPending && - !ShutdownRequestPending && + !IsInterruptPending(INTERRUPT_SHUTDOWN_XLOG) && + !IsInterruptPending(INTERRUPT_SHUTDOWN_AUX) && !ImmediateCheckpointRequested() && IsCheckpointOnSchedule(progress)) { - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* update shmem copies of config variables */ UpdateSharedMemoryConfig(); @@ -804,7 +813,7 @@ CheckpointWriteDelay(int flags, double progress) * Checkpointer and bgwriter are no longer related so take the Big * Sleep. */ - WaitInterrupt(INTERRUPT_GENERAL, + WaitInterrupt(INTERRUPT_GENERAL | INTERRUPT_CONFIG_RELOAD, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT, 100, WAIT_EVENT_CHECKPOINT_WRITE_DELAY); @@ -822,7 +831,7 @@ CheckpointWriteDelay(int flags, double progress) } /* Check for barrier events. */ - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); } @@ -916,8 +925,7 @@ IsCheckpointOnSchedule(double progress) static void ReqShutdownXLOG(SIGNAL_ARGS) { - ShutdownXLOGPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_SHUTDOWN_XLOG); } diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c index f325020ade2..778cd67b641 100644 --- a/src/backend/postmaster/interrupt.c +++ b/src/backend/postmaster/interrupt.c @@ -25,9 +25,7 @@ #include "storage/waiteventset.h" #include "utils/guc.h" #include "utils/memutils.h" - -volatile sig_atomic_t ConfigReloadPending = false; -volatile sig_atomic_t ShutdownRequestPending = false; +#include "utils/resowner.h" /* A common WaitEventSet used to implement WaitInterrupt() */ static WaitEventSet *InterruptWaitSet; @@ -40,6 +38,10 @@ static pg_atomic_uint32 LocalPendingInterrupts; pg_atomic_uint32 *MyPendingInterrupts = &LocalPendingInterrupts; +volatile uint32 InterruptHoldoffCount = 0; +volatile uint32 QueryCancelHoldoffCount = 0; +volatile uint32 CritSectionCount = 0; + /* * Switch to local interrupts. Other backends can't send interrupts to this * one. Only RaiseInterrupt() can set them, from inside this process. @@ -129,6 +131,8 @@ SendInterrupt(uint32 interruptMask, ProcNumber pgprocno) proc = &ProcGlobal->allProcs[pgprocno]; old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, interruptMask); + /* need a memory barrier here? Or is WakeupOtherProc() sufficient? */ + /* * If the process is currently blocked waiting for an interrupt to arrive, * and the interrupt wasn't already pending, wake it up. @@ -283,20 +287,17 @@ WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock, void ProcessMainLoopInterrupts(void) { - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) proc_exit(0); /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); } @@ -304,14 +305,13 @@ ProcessMainLoopInterrupts(void) * Simple signal handler for triggering a configuration reload. * * Normally, this handler would be used for SIGHUP. The idea is that code - * which uses it would arrange to check the ConfigReloadPending flag at - * convenient places inside main loops, or else call ProcessMainLoopInterrupts. + * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt at + * convenient places inside main loops, or else call HandleMainLoopInterrupts. */ void SignalHandlerForConfigReload(SIGNAL_ARGS) { - ConfigReloadPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_CONFIG_RELOAD); } /* @@ -348,12 +348,11 @@ SignalHandlerForCrashExit(SIGNAL_ARGS) * writer and the logical replication parallel apply worker exits on either * SIGINT or SIGTERM. * - * ShutdownRequestPending should be checked at a convenient place within the + * INTERRUPT_SHUTDOWN_AUX should be checked at a convenient place within the * main loop, or else the main loop should call ProcessMainLoopInterrupts. */ void SignalHandlerForShutdownRequest(SIGNAL_ARGS) { - ShutdownRequestPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_SHUTDOWN_AUX); } diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c index 4f28ea5b613..faa8e8acbd2 100644 --- a/src/backend/postmaster/pgarch.c +++ b/src/backend/postmaster/pgarch.c @@ -230,7 +230,7 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, pgarch_waken_stop); /* Reset some signals that are accepted by postmaster but not here */ @@ -331,7 +331,7 @@ pgarch_MainLoop(void) * idea. If more than 60 seconds pass since SIGTERM, exit anyway, so * that the postmaster can start a new archiver if needed. */ - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) { time_t curtime = time(NULL); @@ -353,7 +353,11 @@ pgarch_MainLoop(void) { int rc; - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_GENERAL | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_BARRIER | + INTERRUPT_LOG_MEMORY_CONTEXT, WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH, PGARCH_AUTOWAKE_INTERVAL * 1000L, WAIT_EVENT_ARCHIVER_MAIN); @@ -405,7 +409,7 @@ pgarch_ArchiverCopyLoop(void) * command, and the second is to avoid conflicts with another * archiver spawned by a newer postmaster. */ - if (ShutdownRequestPending || !PostmasterIsAlive()) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX) || !PostmasterIsAlive()) return; /* @@ -856,19 +860,19 @@ pgarch_die(int code, Datum arg) static void ProcessPgArchInterrupts(void) { - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); - if (ConfigReloadPending) + if (IsInterruptPending(INTERRUPT_CONFIG_RELOAD)) { char *archiveLib = pstrdup(XLogArchiveLibrary); bool archiveLibChanged; - ConfigReloadPending = false; + ClearInterrupt(INTERRUPT_CONFIG_RELOAD); ProcessConfigFile(PGC_SIGHUP); if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0') diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index f1332eb2195..ef27558aac2 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -1621,7 +1621,8 @@ ConfigurePostmasterWaitSet(bool accept_connections) pm_wait_set = CreateWaitEventSet(NULL, accept_connections ? (1 + NumListenSockets) : 1); AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET, - INTERRUPT_GENERAL, NULL); + INTERRUPT_CONFIG_RELOAD | INTERRUPT_GENERAL, + NULL); if (accept_connections) { diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c index f2ac862513b..abc4e80f1d6 100644 --- a/src/backend/postmaster/startup.c +++ b/src/backend/postmaster/startup.c @@ -25,6 +25,7 @@ #include "libpq/pqsignal.h" #include "miscadmin.h" #include "postmaster/auxprocess.h" +#include "postmaster/interrupt.h" #include "postmaster/startup.h" #include "storage/ipc.h" #include "storage/pmsignal.h" @@ -50,7 +51,6 @@ /* * Flags set by interrupt handlers for later service in the redo loop. */ -static volatile sig_atomic_t got_SIGHUP = false; static volatile sig_atomic_t shutdown_requested = false; static volatile sig_atomic_t promote_signaled = false; @@ -101,8 +101,7 @@ StartupProcTriggerHandler(SIGNAL_ARGS) static void StartupProcSigHupHandler(SIGNAL_ARGS) { - got_SIGHUP = true; - WakeupRecovery(); + RaiseInterrupt(INTERRUPT_CONFIG_RELOAD); } /* SIGTERM: set flag to abort redo and exit */ @@ -161,11 +160,8 @@ ProcessStartupProcInterrupts(void) /* * Process any requests or signals received recently. */ - if (got_SIGHUP) - { - got_SIGHUP = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) StartupRereadConfig(); - } /* * Check if we were requested to exit without finishing recovery. @@ -187,11 +183,11 @@ ProcessStartupProcInterrupts(void) exit(1); /* Process barrier events */ - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); } @@ -241,7 +237,7 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, StartupProcTriggerHandler); /* diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c index c4c04ae86d4..e10977869b6 100644 --- a/src/backend/postmaster/syslogger.c +++ b/src/backend/postmaster/syslogger.c @@ -337,7 +337,10 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len) * (including the postmaster). */ wes = CreateWaitEventSet(NULL, 2); - AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET, INTERRUPT_GENERAL, NULL); + AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET, + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL, /* for log rotation */ + NULL); #ifndef WIN32 AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], 0, NULL); #endif @@ -360,9 +363,8 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len) /* * Process any requests or signals received recently. */ - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c index 3381767149d..51b2b6d9dd5 100644 --- a/src/backend/postmaster/walsummarizer.c +++ b/src/backend/postmaster/walsummarizer.c @@ -250,7 +250,7 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); /* not used */ /* Advertise ourselves. */ @@ -315,7 +315,12 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len) * So a really fast retry time doesn't seem to be especially * beneficial, and it will clutter the logs. */ - (void) WaitInterrupt(0, WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10000, + (void) WaitInterrupt(INTERRUPT_GENERAL | + INTERRUPT_BARRIER | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_LOG_MEMORY_CONTEXT, + WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10000, WAIT_EVENT_WAL_SUMMARIZER_ERROR); } @@ -855,16 +860,13 @@ GetLatestLSN(TimeLineID *tli) static void ProcessWalSummarizerInterrupts(void) { - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } - if (ShutdownRequestPending || !summarize_wal) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX) || !summarize_wal) { ereport(DEBUG1, errmsg_internal("WAL summarizer shutting down")); @@ -872,7 +874,7 @@ ProcessWalSummarizerInterrupts(void) } /* Perform logging of memory contexts of this process */ - if (LogMemoryContextPending) + if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT)) ProcessLogMemoryContextInterrupt(); } @@ -1638,7 +1640,11 @@ summarizer_wait_for_wal(void) pgstat_report_wal(false); /* OK, now sleep. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_GENERAL | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_BARRIER | + INTERRUPT_LOG_MEMORY_CONTEXT, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, sleep_quanta * MS_PER_SLEEP_QUANTUM, WAIT_EVENT_WAL_SUMMARIZER_WAL); diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c index ae097472200..341a4a5308e 100644 --- a/src/backend/postmaster/walwriter.c +++ b/src/backend/postmaster/walwriter.c @@ -56,7 +56,6 @@ #include "storage/fd.h" #include "storage/lwlock.h" #include "storage/proc.h" -#include "storage/procsignal.h" #include "storage/smgr.h" #include "utils/hsearch.h" #include "utils/memutils.h" @@ -108,7 +107,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); /* not used */ /* @@ -235,9 +234,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len) SetWalWriterSleeping(hibernating); } - /* Clear any already-pending wakeups */ - ClearInterrupt(INTERRUPT_GENERAL); - /* Process any signals received recently */ ProcessMainLoopInterrupts(); @@ -263,7 +259,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len) else cur_timeout = WalWriterDelay * HIBERNATE_FACTOR; - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, cur_timeout, WAIT_EVENT_WAL_WRITER_MAIN); diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c index aaa12ccdbd0..3704e762c3b 100644 --- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c +++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c @@ -237,7 +237,8 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical, else io_flag = WL_SOCKET_WRITEABLE; - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK() | + INTERRUPT_SHUTDOWN_AUX, WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag, PQsocket(conn->streamConn), 0, @@ -245,10 +246,7 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical, /* Interrupted? */ if (rc & WL_INTERRUPT) - { - ClearInterrupt(INTERRUPT_GENERAL); ProcessWalRcvInterrupts(); - } /* If socket is ready, advance the libpq state machine */ if (rc & io_flag) @@ -848,7 +846,8 @@ libpqrcv_PQgetResult(PGconn *streamConn) * since we'll get interrupted by signals and can handle any * interrupts here. */ - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK() | + INTERRUPT_SHUTDOWN_AUX, WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE | WL_INTERRUPT, PQsocket(streamConn), @@ -857,10 +856,7 @@ libpqrcv_PQgetResult(PGconn *streamConn) /* Interrupted? */ if (rc & WL_INTERRUPT) - { - ClearInterrupt(INTERRUPT_GENERAL); ProcessWalRcvInterrupts(); - } /* Consume whatever data is available from the socket */ if (PQconsumeInput(streamConn) == 0) diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c index 176deb406c6..93eeaeda0b2 100644 --- a/src/backend/replication/logical/applyparallelworker.c +++ b/src/backend/replication/logical/applyparallelworker.c @@ -238,12 +238,6 @@ static List *ParallelApplyWorkerPool = NIL; */ ParallelApplyWorkerShared *MyParallelShared = NULL; -/* - * Is there a message sent by a parallel apply worker that the leader apply - * worker needs to receive? - */ -volatile sig_atomic_t ParallelApplyMessagePending = false; - /* * Cache the parallel apply worker information required for applying the * current streaming transaction. It is used to save the cost of searching the @@ -713,7 +707,7 @@ ProcessParallelApplyInterrupts(void) { CHECK_FOR_INTERRUPTS(); - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) { ereport(LOG, (errmsg("logical replication parallel apply worker for subscription \"%s\" has finished", @@ -722,11 +716,8 @@ ProcessParallelApplyInterrupts(void) proc_exit(0); } - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } } /* Parallel apply worker main loop. */ @@ -804,7 +795,10 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh) int rc; /* Wait for more work. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 1000L, WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN); @@ -843,9 +837,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh) static void pa_shutdown(int code, Datum arg) { - SendProcSignal(MyLogicalRepWorker->leader_pid, - PROCSIG_PARALLEL_APPLY_MESSAGE, - INVALID_PROC_NUMBER); + SendInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE, + MyLogicalRepWorker->leader_pgprocno); dsm_detach((dsm_segment *) DatumGetPointer(arg)); } @@ -933,8 +926,7 @@ ParallelApplyWorkerMain(Datum main_arg) error_mqh = shm_mq_attach(mq, seg, NULL); pq_redirect_to_shm_mq(seg, error_mqh); - pq_set_parallel_leader(MyLogicalRepWorker->leader_pid, - INVALID_PROC_NUMBER); + pq_set_parallel_leader(MyLogicalRepWorker->leader_pgprocno); MyLogicalRepWorker->last_send_time = MyLogicalRepWorker->last_recv_time = MyLogicalRepWorker->reply_time = 0; @@ -978,21 +970,6 @@ ParallelApplyWorkerMain(Datum main_arg) Assert(false); } -/* - * Handle receipt of an interrupt indicating a parallel apply worker message. - * - * Note: this is called within a signal handler! All we can do is set a flag - * that will cause the next CHECK_FOR_INTERRUPTS() to invoke - * ProcessParallelApplyMessages(). - */ -void -HandleParallelApplyMessageInterrupt(void) -{ - InterruptPending = true; - ParallelApplyMessagePending = true; - RaiseInterrupt(INTERRUPT_GENERAL); -} - /* * Process a single protocol message received from a single parallel apply * worker. @@ -1057,7 +1034,7 @@ ProcessParallelApplyMessage(StringInfo msg) } /* - * Handle any queued protocol messages received from parallel apply workers. + * Process any queued protocol messages received from parallel apply workers. */ void ProcessParallelApplyMessages(void) @@ -1090,7 +1067,7 @@ ProcessParallelApplyMessages(void) oldcontext = MemoryContextSwitchTo(hpam_context); - ParallelApplyMessagePending = false; + ClearInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE); foreach(lc, ParallelApplyWorkerPool) { @@ -1182,15 +1159,15 @@ pa_send_data(ParallelApplyWorkerInfo *winfo, Size nbytes, const void *data) Assert(result == SHM_MQ_WOULD_BLOCK); /* Wait before retrying. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, SHM_SEND_RETRY_INTERVAL_MS, WAIT_EVENT_LOGICAL_APPLY_SEND_DATA); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } if (startTime == 0) @@ -1254,16 +1231,16 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo, break; /* Wait to be signalled. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10L, WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE); - /* Clear the interrupt flag so we don't spin. */ - ClearInterrupt(INTERRUPT_GENERAL); - /* An interrupt may have occurred while we were waiting. */ CHECK_FOR_INTERRUPTS(); + + /* Clear the interrupt flag so we don't spin. */ + ClearInterrupt(INTERRUPT_GENERAL); } } diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c index 168112a3220..76620c80cfb 100644 --- a/src/backend/replication/logical/launcher.c +++ b/src/backend/replication/logical/launcher.c @@ -213,14 +213,14 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker, * interrupt about the worker attach. But we don't expect to have to * wait long. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10L, WAIT_EVENT_BGWORKER_STARTUP); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } } } @@ -441,6 +441,7 @@ retry: worker->relstate_lsn = InvalidXLogRecPtr; worker->stream_fileset = NULL; worker->leader_pid = is_parallel_apply_worker ? MyProcPid : InvalidPid; + worker->leader_pgprocno = is_parallel_apply_worker ? MyProcNumber : INVALID_PROC_NUMBER; worker->parallel_apply = is_parallel_apply_worker; worker->last_lsn = InvalidXLogRecPtr; TIMESTAMP_NOBEGIN(worker->last_send_time); @@ -521,7 +522,7 @@ retry: * slot. */ static void -logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo) +logicalrep_worker_stop_internal(LogicalRepWorker *worker, int interrupt) { uint16 generation; @@ -544,14 +545,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo) LWLockRelease(LogicalRepWorkerLock); /* Wait a bit --- we don't expect to have to wait long. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10L, WAIT_EVENT_BGWORKER_STARTUP); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } /* Recheck worker status. */ @@ -571,7 +572,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo) } /* Now terminate the worker ... */ - kill(worker->proc->pid, signo); + SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc)); /* ... and wait for it to die. */ for (;;) @@ -585,14 +586,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo) LWLockRelease(LogicalRepWorkerLock); /* Wait a bit --- we don't expect to have to wait long. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10L, WAIT_EVENT_BGWORKER_SHUTDOWN); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } LWLockAcquire(LogicalRepWorkerLock, LW_SHARED); @@ -614,7 +615,7 @@ logicalrep_worker_stop(Oid subid, Oid relid) if (worker) { Assert(!isParallelApplyWorker(worker)); - logicalrep_worker_stop_internal(worker, SIGTERM); + logicalrep_worker_stop_internal(worker, INTERRUPT_DIE); } LWLockRelease(LogicalRepWorkerLock); @@ -661,13 +662,14 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo) * Only stop the worker if the generation matches and the worker is alive. */ if (worker->generation == generation && worker->proc) - logicalrep_worker_stop_internal(worker, SIGINT); + logicalrep_worker_stop_internal(worker, INTERRUPT_QUERY_CANCEL); LWLockRelease(LogicalRepWorkerLock); } /* - * Wake up (using interrupt) any logical replication worker for specified sub/rel. + * Wake up (using INTERRUPT_GENERAL) any logical replication worker for + * specified sub/rel. */ void logicalrep_worker_wakeup(Oid subid, Oid relid) @@ -762,7 +764,7 @@ logicalrep_worker_detach(void) LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc); if (isParallelApplyWorker(w)) - logicalrep_worker_stop_internal(w, SIGTERM); + logicalrep_worker_stop_internal(w, INTERRUPT_DIE); } LWLockRelease(LogicalRepWorkerLock); @@ -792,6 +794,7 @@ logicalrep_worker_cleanup(LogicalRepWorker *worker) worker->subid = InvalidOid; worker->relid = InvalidOid; worker->leader_pid = InvalidPid; + worker->leader_pgprocno = INVALID_PROC_NUMBER; worker->parallel_apply = false; } @@ -1217,21 +1220,20 @@ ApplyLauncherMain(Datum main_arg) MemoryContextDelete(subctx); /* Wait for more work. */ - rc = WaitInterrupt(INTERRUPT_GENERAL | INTERRUPT_SUBSCRIPTION_CHANGE, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_SUBSCRIPTION_CHANGE | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, wait_time, WAIT_EVENT_LOGICAL_LAUNCHER_MAIN); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); - } - - if (ConfigReloadPending) - { - ConfigReloadPending = false; - ProcessConfigFile(PGC_SIGHUP); + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) + ProcessConfigFile(PGC_SIGHUP); + ClearInterrupt(INTERRUPT_GENERAL); } } diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c index 09b2d0d181e..4e564752003 100644 --- a/src/backend/replication/logical/slotsync.c +++ b/src/backend/replication/logical/slotsync.c @@ -72,9 +72,9 @@ /* * Struct for sharing information to control slot synchronization. * - * The slot sync worker's pid is needed by the startup process to shut it - * down during promotion. The startup process shuts down the slot sync worker - * and also sets stopSignaled=true to handle the race condition when the + * The slot sync worker's proc number is needed by the startup process to shut + * it down during promotion. The startup process shuts down the slot sync + * worker and also sets stopSignaled=true to handle the race condition when the * postmaster has not noticed the promotion yet and thus may end up restarting * the slot sync worker. If stopSignaled is set, the worker will exit in such a * case. The SQL function pg_sync_replication_slots() will also error out if @@ -94,7 +94,7 @@ */ typedef struct SlotSyncCtxStruct { - pid_t pid; + ProcNumber procno; bool stopSignaled; bool syncing; time_t last_start_time; @@ -1112,7 +1112,7 @@ slotsync_reread_config(void) Assert(sync_replication_slots); - ConfigReloadPending = false; + ClearInterrupt(INTERRUPT_CONFIG_RELOAD); ProcessConfigFile(PGC_SIGHUP); conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0; @@ -1154,7 +1154,7 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn) { CHECK_FOR_INTERRUPTS(); - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_DIE)) { ereport(LOG, errmsg("replication slot synchronization worker is shutting down on receiving SIGINT")); @@ -1162,7 +1162,7 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn) proc_exit(0); } - if (ConfigReloadPending) + if (IsInterruptPending(INTERRUPT_CONFIG_RELOAD)) slotsync_reread_config(); } @@ -1206,7 +1206,7 @@ slotsync_worker_onexit(int code, Datum arg) SpinLockAcquire(&SlotSyncCtx->mutex); - SlotSyncCtx->pid = InvalidPid; + SlotSyncCtx->procno = INVALID_PROC_NUMBER; /* * If syncing_slots is true, it indicates that the process errored out @@ -1252,7 +1252,9 @@ wait_for_slot_activity(bool some_slot_updated) sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS; } - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | + INTERRUPT_GENERAL | + INTERRUPT_CONFIG_RELOAD, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, sleep_ms, WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN); @@ -1266,12 +1268,12 @@ wait_for_slot_activity(bool some_slot_updated) * Otherwise, advertise that a sync is in progress. */ static void -check_and_set_sync_info(pid_t worker_pid) +check_and_set_sync_info(ProcNumber worker_procno) { SpinLockAcquire(&SlotSyncCtx->mutex); /* The worker pid must not be already assigned in SlotSyncCtx */ - Assert(worker_pid == InvalidPid || SlotSyncCtx->pid == InvalidPid); + Assert(worker_procno == INVALID_PROC_NUMBER || SlotSyncCtx->procno == INVALID_PROC_NUMBER); /* * Emit an error if startup process signaled the slot sync machinery to @@ -1296,10 +1298,10 @@ check_and_set_sync_info(pid_t worker_pid) SlotSyncCtx->syncing = true; /* - * Advertise the required PID so that the startup process can kill the - * slot sync worker on promotion. + * Advertise the required proc number so that the startup process can kill + * the slot sync worker on promotion. */ - SlotSyncCtx->pid = worker_pid; + SlotSyncCtx->procno = worker_procno; SpinLockRelease(&SlotSyncCtx->mutex); @@ -1391,16 +1393,16 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len) pqsignal(SIGINT, SignalHandlerForShutdownRequest); pqsignal(SIGTERM, die); pqsignal(SIGFPE, FloatExceptionHandler); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); pqsignal(SIGCHLD, SIG_DFL); - check_and_set_sync_info(MyProcPid); + check_and_set_sync_info(MyProcNumber); ereport(LOG, errmsg("slot sync worker started")); - /* Register it as soon as SlotSyncCtx->pid is initialized. */ + /* Register it as soon as SlotSyncCtx->procno is initialized. */ before_shmem_exit(slotsync_worker_onexit, (Datum) 0); /* @@ -1521,7 +1523,7 @@ update_synced_slots_inactive_since(void) return; /* The slot sync worker or SQL function mustn't be running by now */ - Assert((SlotSyncCtx->pid == InvalidPid) && !SlotSyncCtx->syncing); + Assert((SlotSyncCtx->procno == INVALID_PROC_NUMBER) && !SlotSyncCtx->syncing); LWLockAcquire(ReplicationSlotControlLock, LW_SHARED); @@ -1535,7 +1537,7 @@ update_synced_slots_inactive_since(void) Assert(SlotIsLogical(s)); /* The slot must not be acquired by any process */ - Assert(s->active_pid == 0); + Assert(s->active_proc == INVALID_PROC_NUMBER); /* Use the same inactive_since time for all the slots. */ if (now == 0) @@ -1558,7 +1560,7 @@ update_synced_slots_inactive_since(void) void ShutDownSlotSync(void) { - pid_t worker_pid; + ProcNumber worker_procno; SpinLockAcquire(&SlotSyncCtx->mutex); @@ -1575,12 +1577,13 @@ ShutDownSlotSync(void) return; } - worker_pid = SlotSyncCtx->pid; + worker_procno = SlotSyncCtx->procno; SpinLockRelease(&SlotSyncCtx->mutex); - if (worker_pid != InvalidPid) - kill(worker_pid, SIGINT); + /* XXX: we're not holding any locks here. The PGPROC could get recycled for another backend */ + if (worker_procno != INVALID_PROC_NUMBER) + SendInterrupt(INTERRUPT_QUERY_CANCEL, worker_procno); /* Wait for slot sync to end */ for (;;) @@ -1588,14 +1591,14 @@ ShutDownSlotSync(void) int rc; /* Wait a bit, we don't expect to have to wait long */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } SpinLockAcquire(&SlotSyncCtx->mutex); @@ -1673,7 +1676,7 @@ SlotSyncShmemInit(void) if (!found) { memset(SlotSyncCtx, 0, size); - SlotSyncCtx->pid = InvalidPid; + SlotSyncCtx->procno = INVALID_PROC_NUMBER; SpinLockInit(&SlotSyncCtx->mutex); } } @@ -1723,7 +1726,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn) { PG_ENSURE_ERROR_CLEANUP(slotsync_failure_callback, PointerGetDatum(wrconn)); { - check_and_set_sync_info(InvalidPid); + check_and_set_sync_info(INVALID_PROC_NUMBER); validate_remote_info(wrconn); diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c index dc9077adb75..5a938d61827 100644 --- a/src/backend/replication/logical/tablesync.c +++ b/src/backend/replication/logical/tablesync.c @@ -211,7 +211,7 @@ wait_for_relation_state_change(Oid relid, char expected_state) if (!worker) break; - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE); @@ -261,10 +261,10 @@ wait_for_worker_state_change(char expected_state) break; /* - * Wait. We expect to get an interrupt wakeup from the apply worker, - * but use a timeout in case it dies without sending one. + * Wait. We expect to get an INTERRUPT_GENERAL wakeup from the apply + * worker, but use a timeout in case it dies without sending one. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE); @@ -774,12 +774,10 @@ copy_read_data(void *outbuf, int minread, int maxread) /* * Wait for more data or interrupt. */ - (void) WaitInterruptOrSocket(INTERRUPT_GENERAL, + (void) WaitInterruptOrSocket(INTERRUPT_CFI_MASK(), WL_SOCKET_READABLE | WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA); - - ClearInterrupt(INTERRUPT_GENERAL); } return bytesread; diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c index bb566c1ee19..4c136e73a62 100644 --- a/src/backend/replication/logical/worker.c +++ b/src/backend/replication/logical/worker.c @@ -3654,11 +3654,8 @@ LogicalRepApplyLoop(XLogRecPtr last_received) int c; StringInfoData s; - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } /* Reset timeout. */ last_recv_timestamp = GetCurrentTimestamp(); @@ -3753,7 +3750,9 @@ LogicalRepApplyLoop(XLogRecPtr last_received) else wait_time = NAPTIME_PER_CYCLE; - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL, WL_SOCKET_READABLE | WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, fd, wait_time, @@ -3761,14 +3760,10 @@ LogicalRepApplyLoop(XLogRecPtr last_received) if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); - } - - if (ConfigReloadPending) - { - ConfigReloadPending = false; - ProcessConfigFile(PGC_SIGHUP); + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) + ProcessConfigFile(PGC_SIGHUP); + ClearInterrupt(INTERRUPT_GENERAL); } if (rc & WL_TIMEOUT) diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 719e531eb90..5ce7f88f216 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -224,6 +224,7 @@ ReplicationSlotsShmemInit(void) ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i]; /* everything else is zeroed by the memset above */ + slot->active_proc = INVALID_PROC_NUMBER; SpinLockInit(&slot->mutex); LWLockInitialize(&slot->io_in_progress_lock, LWTRANCHE_REPLICATION_SLOT_IO); @@ -402,7 +403,7 @@ ReplicationSlotCreate(const char *name, bool db_specific, * be doing that. So it's safe to initialize the slot. */ Assert(!slot->in_use); - Assert(slot->active_pid == 0); + Assert(slot->active_proc == INVALID_PROC_NUMBER); /* first initialize persistent data */ memset(&slot->data, 0, sizeof(ReplicationSlotPersistentData)); @@ -444,8 +445,8 @@ ReplicationSlotCreate(const char *name, bool db_specific, /* We can now mark the slot active, and that makes it our slot. */ SpinLockAcquire(&slot->mutex); - Assert(slot->active_pid == 0); - slot->active_pid = MyProcPid; + Assert(slot->active_proc == INVALID_PROC_NUMBER); + slot->active_proc = MyProcNumber; SpinLockRelease(&slot->mutex); MyReplicationSlot = slot; @@ -559,7 +560,7 @@ void ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid) { ReplicationSlot *s; - int active_pid; + ProcNumber active_proc; Assert(name != NULL); @@ -600,15 +601,15 @@ retry: * to inactive_since in InvalidatePossiblyObsoleteSlot. */ SpinLockAcquire(&s->mutex); - if (s->active_pid == 0) - s->active_pid = MyProcPid; - active_pid = s->active_pid; + if (s->active_proc == INVALID_PROC_NUMBER) + s->active_proc = MyProcNumber; + active_proc = s->active_proc; ReplicationSlotSetInactiveSince(s, 0, false); SpinLockRelease(&s->mutex); } else { - active_pid = MyProcPid; + active_proc = MyProcNumber; ReplicationSlotSetInactiveSince(s, 0, true); } LWLockRelease(ReplicationSlotControlLock); @@ -618,7 +619,7 @@ retry: * wait until the owning process signals us that it's been released, or * error out. */ - if (active_pid != MyProcPid) + if (active_proc != MyProcNumber) { if (!nowait) { @@ -632,7 +633,7 @@ retry: ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("replication slot \"%s\" is active for PID %d", - NameStr(s->data.name), active_pid))); + NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid))); } else if (!nowait) ConditionVariableCancelSleep(); /* no sleep needed after all */ @@ -690,7 +691,7 @@ ReplicationSlotRelease(void) bool is_logical = false; /* keep compiler quiet */ TimestampTz now = 0; - Assert(slot != NULL && slot->active_pid != 0); + Assert(slot != NULL && slot->active_proc != INVALID_PROC_NUMBER); if (am_walsender) { @@ -736,7 +737,7 @@ ReplicationSlotRelease(void) * disconnecting, but wake up others that may be waiting for it. */ SpinLockAcquire(&slot->mutex); - slot->active_pid = 0; + slot->active_proc = INVALID_PROC_NUMBER; ReplicationSlotSetInactiveSince(slot, now, false); SpinLockRelease(&slot->mutex); ConditionVariableBroadcast(&slot->active_cv); @@ -788,7 +789,7 @@ restart: continue; SpinLockAcquire(&s->mutex); - if ((s->active_pid == MyProcPid && + if ((s->active_proc == MyProcNumber && (!synced_only || s->data.synced))) { Assert(s->data.persistency == RS_TEMPORARY); @@ -976,7 +977,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot) bool fail_softly = slot->data.persistency != RS_PERSISTENT; SpinLockAcquire(&slot->mutex); - slot->active_pid = 0; + slot->active_proc = INVALID_PROC_NUMBER; SpinLockRelease(&slot->mutex); /* wake up anyone waiting on this slot */ @@ -998,7 +999,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot) * Also wake up processes waiting for it. */ LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE); - slot->active_pid = 0; + slot->active_proc = INVALID_PROC_NUMBER; slot->in_use = false; LWLockRelease(ReplicationSlotControlLock); ConditionVariableBroadcast(&slot->active_cv); @@ -1293,7 +1294,7 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive) /* count slots with spinlock held */ SpinLockAcquire(&s->mutex); (*nslots)++; - if (s->active_pid != 0) + if (s->active_proc != INVALID_PROC_NUMBER) (*nactive)++; SpinLockRelease(&s->mutex); } @@ -1331,7 +1332,7 @@ restart: { ReplicationSlot *s; char *slotname; - int active_pid; + ProcNumber active_proc; s = &ReplicationSlotCtl->replication_slots[i]; @@ -1353,11 +1354,11 @@ restart: SpinLockAcquire(&s->mutex); /* can't change while ReplicationSlotControlLock is held */ slotname = NameStr(s->data.name); - active_pid = s->active_pid; - if (active_pid == 0) + active_proc = s->active_proc; + if (active_proc == INVALID_PROC_NUMBER) { MyReplicationSlot = s; - s->active_pid = MyProcPid; + s->active_proc = MyProcNumber; } SpinLockRelease(&s->mutex); @@ -1382,11 +1383,11 @@ restart: * XXX: We can consider shutting down the slot sync worker before * trying to drop synced temporary slots here. */ - if (active_pid) + if (active_proc != INVALID_PROC_NUMBER) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("replication slot \"%s\" is active for PID %d", - slotname, active_pid))); + slotname, GetPGProcByNumber(active_proc)->pid))); /* * To avoid duplicating ReplicationSlotDropAcquired() and to avoid @@ -1734,7 +1735,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, { XLogRecPtr restart_lsn; NameData slotname; - int active_pid = 0; + ProcNumber active_proc = INVALID_PROC_NUMBER; ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE; TimestampTz now = 0; long slot_idle_secs = 0; @@ -1817,17 +1818,17 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, } slotname = s->data.name; - active_pid = s->active_pid; + active_proc = s->active_proc; /* * If the slot can be acquired, do so and mark it invalidated * immediately. Otherwise we'll signal the owning process, below, and * retry. */ - if (active_pid == 0) + if (active_proc == INVALID_PROC_NUMBER) { MyReplicationSlot = s; - s->active_pid = MyProcPid; + s->active_proc = MyProcNumber; s->data.invalidated = invalidation_cause; /* @@ -1864,8 +1865,10 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, &slot_idle_usecs); } - if (active_pid != 0) + if (active_proc != INVALID_PROC_NUMBER) { + int active_pid; + /* * Prepare the sleep on the slot's condition variable before * releasing the lock, to close a possible race condition if the @@ -1873,7 +1876,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, */ ConditionVariablePrepareToSleep(&s->active_cv); - LWLockRelease(ReplicationSlotControlLock); released_lock = true; /* @@ -1882,31 +1884,38 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, * signalling is the only reason for there to be a loop in this * routine; otherwise we could rely on caller's restart loop.) * + * Hold the lock while doing this, to prevent the PGPROC entry + * from being recycled while we do this; PGPROC slots do get + * reused quickly. + * * There is the race condition that other process may own the slot * after its current owner process is terminated and before this * process owns it. To handle that, we signal only if the PID of * the owning process has changed from the previous time. (This * logic assumes that the same PID is not reused very quickly.) */ + active_pid = GetPGProcByNumber(active_proc)->pid; if (last_signaled_pid != active_pid) { - ReportSlotInvalidation(invalidation_cause, true, active_pid, + ReportSlotInvalidation(invalidation_cause, true, + active_pid, slotname, restart_lsn, oldestLSN, snapshotConflictHorizon, slot_idle_secs); if (MyBackendType == B_STARTUP) - (void) SendProcSignal(active_pid, - PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT, - INVALID_PROC_NUMBER); + SendInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT, + active_proc); else - (void) kill(active_pid, SIGTERM); + SendInterrupt(INTERRUPT_DIE, active_proc); last_signaled_pid = active_pid; terminated = true; invalidation_cause_prev = invalidation_cause; } + LWLockRelease(ReplicationSlotControlLock); + /* Wait until the slot is released. */ ConditionVariableSleep(&s->active_cv, WAIT_EVENT_REPLICATION_SLOT_DROP); @@ -1937,7 +1946,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes, ReplicationSlotSave(); ReplicationSlotRelease(); - ReportSlotInvalidation(invalidation_cause, false, active_pid, + ReportSlotInvalidation(invalidation_cause, false, + GetPGProcByNumber(active_proc)->pid, slotname, restart_lsn, oldestLSN, snapshotConflictHorizon, slot_idle_secs); @@ -2576,7 +2586,7 @@ RestoreSlotFromDisk(const char *name) slot->candidate_restart_valid = InvalidXLogRecPtr; slot->in_use = true; - slot->active_pid = 0; + slot->active_proc = INVALID_PROC_NUMBER; /* * Set the time since the slot has become inactive after loading the @@ -2884,7 +2894,7 @@ StandbySlotsHaveCaughtup(XLogRecPtr wait_for_lsn, int elevel) SpinLockAcquire(&slot->mutex); restart_lsn = slot->data.restart_lsn; invalidated = slot->data.invalidated != RS_INVAL_NONE; - inactive = slot->active_pid == 0; + inactive = slot->active_proc == INVALID_PROC_NUMBER; SpinLockRelease(&slot->mutex); if (invalidated) @@ -2970,11 +2980,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn) { CHECK_FOR_INTERRUPTS(); - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } /* Exit if done waiting for every slot. */ if (StandbySlotsHaveCaughtup(wait_for_lsn, WARNING)) @@ -2984,6 +2991,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn) * Wait for the slots in the synchronized_standby_slots to catch up, * but use a timeout (1s) so we can also check if the * synchronized_standby_slots has been changed. + * + * FIXME: I think we need a wait to add a CV to WaitEventSet */ ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000, WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION); diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c index 146eef5871e..6820b131465 100644 --- a/src/backend/replication/slotfuncs.c +++ b/src/backend/replication/slotfuncs.c @@ -20,6 +20,7 @@ #include "replication/logical.h" #include "replication/slot.h" #include "replication/slotsync.h" +#include "storage/proc.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/pg_lsn.h" @@ -255,6 +256,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) { ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno]; ReplicationSlot slot_contents; + int active_pid; Datum values[PG_GET_REPLICATION_SLOTS_COLS]; bool nulls[PG_GET_REPLICATION_SLOTS_COLS]; WALAvailability walstate; @@ -267,6 +269,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) /* Copy slot contents while holding spinlock, then examine at leisure */ SpinLockAcquire(&slot->mutex); slot_contents = *slot; + if (slot_contents.active_proc != INVALID_PROC_NUMBER) + active_pid = GetPGProcByNumber(slot_contents.active_proc)->pid; + else + active_pid = 0; SpinLockRelease(&slot->mutex); memset(values, 0, sizeof(values)); @@ -291,10 +297,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) values[i++] = ObjectIdGetDatum(slot_contents.data.database); values[i++] = BoolGetDatum(slot_contents.data.persistency == RS_TEMPORARY); - values[i++] = BoolGetDatum(slot_contents.active_pid != 0); + values[i++] = BoolGetDatum(slot_contents.active_proc != INVALID_PROC_NUMBER); - if (slot_contents.active_pid != 0) - values[i++] = Int32GetDatum(slot_contents.active_pid); + if (slot_contents.active_proc != INVALID_PROC_NUMBER) + values[i++] = Int32GetDatum(active_pid); else nulls[i++] = true; @@ -359,13 +365,13 @@ pg_get_replication_slots(PG_FUNCTION_ARGS) */ if (!XLogRecPtrIsInvalid(slot_contents.data.restart_lsn)) { - int pid; + ProcNumber procno; SpinLockAcquire(&slot->mutex); - pid = slot->active_pid; + procno = slot->active_proc; slot_contents.data.restart_lsn = slot->data.restart_lsn; SpinLockRelease(&slot->mutex); - if (pid != 0) + if (procno != INVALID_PROC_NUMBER) { values[i++] = CStringGetTextDatum("unreserved"); walstate = WALAVAIL_UNRESERVED; diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c index 93d8b9d0605..d977c3a5b23 100644 --- a/src/backend/replication/syncrep.c +++ b/src/backend/replication/syncrep.c @@ -252,10 +252,10 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) * entitled to assume that an acknowledged commit is also replicated, * which might not be true. So in this case we issue a WARNING (which * some clients may be able to interpret) and shut off further output. - * We do NOT reset ProcDiePending, so that the process will die after + * We do NOT clear the interrupt bit, so that the process will die after * the commit is cleaned up. */ - if (ProcDiePending) + if (IsInterruptPending(INTERRUPT_DIE)) { ereport(WARNING, (errcode(ERRCODE_ADMIN_SHUTDOWN), @@ -272,9 +272,8 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) * altogether is not helpful, so we just terminate the wait with a * suitable warning. */ - if (QueryCancelPending) + if (ConsumeInterrupt(INTERRUPT_QUERY_CANCEL)) { - QueryCancelPending = false; ereport(WARNING, (errmsg("canceling wait for synchronous replication due to user request"), errdetail("The transaction has already committed locally, but might not have been replicated to the standby."))); @@ -286,7 +285,9 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) * Wait on interrupt. Any condition that should wake us up will set * the interrupt, so no need for timeout. */ - rc = WaitInterrupt(INTERRUPT_GENERAL, + rc = WaitInterrupt(INTERRUPT_GENERAL | + INTERRUPT_DIE | + INTERRUPT_QUERY_CANCEL, WL_INTERRUPT | WL_POSTMASTER_DEATH, -1, WAIT_EVENT_SYNC_REP); @@ -297,7 +298,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit) */ if (rc & WL_POSTMASTER_DEATH) { - ProcDiePending = true; + RaiseInterrupt(INTERRUPT_DIE); whereToSendOutput = DestNone; SyncRepCancelWait(); break; diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c index 87b1bad279f..63990360569 100644 --- a/src/backend/replication/walreceiver.c +++ b/src/backend/replication/walreceiver.c @@ -70,7 +70,6 @@ #include "storage/ipc.h" #include "storage/proc.h" #include "storage/procarray.h" -#include "storage/procsignal.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/guc.h" @@ -169,7 +168,7 @@ ProcessWalRcvInterrupts(void) */ CHECK_FOR_INTERRUPTS(); - if (ShutdownRequestPending) + if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX)) { ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), @@ -284,7 +283,7 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len) /* SIGQUIT handler was already set up by InitPostmasterChild */ pqsignal(SIGALRM, SIG_IGN); pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); /* Reset some signals that are accepted by postmaster but not here */ @@ -461,9 +460,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len) /* Process any requests or signals received recently */ ProcessWalRcvInterrupts(); - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); /* recompute wakeup times */ now = GetCurrentTimestamp(); @@ -546,7 +544,10 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len) * avoiding some system calls. */ Assert(wait_fd != PGINVALID_SOCKET); - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK() | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL, WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT | WL_INTERRUPT, wait_fd, @@ -554,7 +555,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len) WAIT_EVENT_WAL_RECEIVER_MAIN); if (rc & WL_INTERRUPT) { - ClearInterrupt(INTERRUPT_GENERAL); ProcessWalRcvInterrupts(); if (walrcv->force_reply) @@ -569,6 +569,7 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len) pg_memory_barrier(); XLogWalRcvSendReply(true, false); } + ClearInterrupt(INTERRUPT_GENERAL); } if (rc & WL_TIMEOUT) { @@ -734,7 +735,9 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI) } SpinLockRelease(&walrcv->mutex); - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | + INTERRUPT_SHUTDOWN_AUX | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_WAL_RECEIVER_WAIT_START); diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 0afa181550b..9c807620b7a 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -25,7 +25,7 @@ * the walsender will exit quickly without sending any more XLOG records. * * If the server is shut down, checkpointer sends us - * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited. If + * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited. If * the backend is idle or runs an SQL query this causes the backend to * shutdown, if logical replication is in progress all existing WAL records * are processed followed by a shutdown. Otherwise this causes the walsender @@ -199,9 +199,9 @@ static volatile sig_atomic_t got_STOPPING = false; /* * This is set while we are streaming. When not set - * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set, - * the main loop is responsible for checking got_STOPPING and terminating when - * it's set (after streaming any remaining WAL). + * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM. When + * set, the main loop is responsible for checking got_STOPPING and terminating + * when it's set (after streaming any remaining WAL). */ static volatile sig_atomic_t replication_active = false; @@ -259,7 +259,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr); static void WalSndKeepaliveIfNecessary(void); static void WalSndCheckTimeOut(void); static long WalSndComputeSleeptime(TimestampTz now); -static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event); +static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask); static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write); static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write); static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, @@ -1608,7 +1608,10 @@ ProcessPendingWrites(void) /* Sleep until something happens or we time out */ WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime, - WAIT_EVENT_WAL_SENDER_WRITE_DATA); + WAIT_EVENT_WAL_SENDER_WRITE_DATA, + INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL); /* Clear any already-pending wakeups */ ClearInterrupt(INTERRUPT_GENERAL); @@ -1616,9 +1619,8 @@ ProcessPendingWrites(void) CHECK_FOR_INTERRUPTS(); /* Process any requests or signals received recently */ - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); SyncRepInitConfig(); } @@ -1628,7 +1630,7 @@ ProcessPendingWrites(void) WalSndShutdown(); } - /* reactivate interrupt so WalSndLoop knows to continue */ + /* reactivate interrupt flag so WalSndLoop knows to continue */ RaiseInterrupt(INTERRUPT_GENERAL); } @@ -1822,9 +1824,8 @@ WalSndWaitForWal(XLogRecPtr loc) CHECK_FOR_INTERRUPTS(); /* Process any requests or signals received recently */ - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); SyncRepInitConfig(); } @@ -1934,7 +1935,10 @@ WalSndWaitForWal(XLogRecPtr loc) Assert(wait_event != 0); - WalSndWait(wakeEvents, sleeptime, wait_event); + WalSndWait(wakeEvents, sleeptime, wait_event, + INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL); } /* reactivate interrupt flag so WalSndLoop knows to continue */ @@ -2759,9 +2763,8 @@ WalSndLoop(WalSndSendDataCallback send_data) CHECK_FOR_INTERRUPTS(); /* Process any requests or signals received recently */ - if (ConfigReloadPending) + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) { - ConfigReloadPending = false; ProcessConfigFile(PGC_SIGHUP); SyncRepInitConfig(); } @@ -2857,7 +2860,11 @@ WalSndLoop(WalSndSendDataCallback send_data) wakeEvents |= WL_SOCKET_WRITEABLE; /* Sleep until something happens or we time out */ - WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN); + WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN, + INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL + ); } } } @@ -2895,6 +2902,7 @@ InitWalSenderSlot(void) /* * Found a free slot. Reserve it for us. */ + walsnd->pgprocno = MyProcNumber; walsnd->pid = MyProcPid; walsnd->state = WALSNDSTATE_STARTUP; walsnd->sentPtr = InvalidXLogRecPtr; @@ -2951,6 +2959,7 @@ WalSndKill(int code, Datum arg) SpinLockAcquire(&walsnd->mutex); /* Mark WalSnd struct as no longer being in use. */ walsnd->pid = 0; + walsnd->pgprocno = 0; SpinLockRelease(&walsnd->mutex); } @@ -3525,10 +3534,10 @@ WalSndRqstFileReload(void) } /* - * Handle PROCSIG_WALSND_INIT_STOPPING signal. + * Process INTERRUPT_WALSND_INIT_STOPPING interrupt */ void -HandleWalSndInitStopping(void) +ProcessWalSndInitStopping(void) { Assert(am_walsender); @@ -3539,9 +3548,12 @@ HandleWalSndInitStopping(void) * standby, and then exit gracefully. */ if (!replication_active) - kill(MyProcPid, SIGTERM); + RaiseInterrupt(INTERRUPT_DIE); else + { got_STOPPING = true; + RaiseInterrupt(INTERRUPT_GENERAL); + } } /* @@ -3567,7 +3579,7 @@ WalSndSignals(void) /* SIGQUIT handler was already set up by InitPostmasterChild */ InitializeTimeouts(); /* establishes SIGALRM handler */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, WalSndLastCycleHandler); /* request a last cycle and * shutdown */ @@ -3655,24 +3667,28 @@ WalSndWakeup(bool physical, bool logical) * on postmaster death. */ static void -WalSndWait(uint32 socket_events, long timeout, uint32 wait_event) +WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask) { WaitEvent event; + /* for condition variables */ + interruptMask |= INTERRUPT_GENERAL; + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0); + ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask); /* * We use a condition variable to efficiently wake up walsenders in * WalSndWakeup(). * * Every walsender prepares to sleep on a shared memory CV. Note that it - * just prepares to sleep on the CV (i.e., adds itself to the CV's - * waitlist), but does not actually wait on the CV (IOW, it never calls - * ConditionVariableSleep()). It still uses WaitEventSetWait() for - * waiting, because we also need to wait for socket events. The processes - * (startup process, walreceiver etc.) wanting to wake up walsenders use - * ConditionVariableBroadcast(), which in turn calls SendInterrupt(), - * helping walsenders come out of WaitEventSetWait(). + * just prepares to sleep on the CV waitlist), but does not actually wait + * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses + * WaitEventSetWait() for waiting, because we also need to wait for socket + * events. The processes (startup process, walreceiver etc.) wanting to + * wake up walsenders use ConditionVariableBroadcast(), which in turn + * calls SendInterrupt(), helping walsenders come out of + * WaitEventSetWait(). * * This approach is simple and efficient because, one doesn't have to loop * through all the walsenders slots, with a spinlock acquisition and @@ -3720,16 +3736,16 @@ WalSndInitStopping(void) for (i = 0; i < max_wal_senders; i++) { WalSnd *walsnd = &WalSndCtl->walsnds[i]; - pid_t pid; + ProcNumber procno; SpinLockAcquire(&walsnd->mutex); - pid = walsnd->pid; + procno = walsnd->pgprocno; SpinLockRelease(&walsnd->mutex); - if (pid == 0) + if (procno == INVALID_PROC_NUMBER) continue; - SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, INVALID_PROC_NUMBER); + SendInterrupt(INTERRUPT_WALSND_INIT_STOPPING, procno); } } diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index d7be4309d4e..4099e141231 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -55,6 +55,7 @@ #include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/proc.h" +#include "storage/procsignal.h" #include "storage/read_stream.h" #include "storage/smgr.h" #include "storage/standby.h" @@ -2976,7 +2977,7 @@ BufferSync(int flags) UnlockBufHdr(bufHdr, buf_state); /* Check for barrier events in case NBuffers is large. */ - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); } @@ -3057,7 +3058,7 @@ BufferSync(int flags) s->num_to_scan++; /* Check for barrier events. */ - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); } @@ -5213,7 +5214,7 @@ LockBufferForCleanup(Buffer buffer) * deadlock_timeout for it. */ if (logged_recovery_conflict) - LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN, + LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN, waitStart, GetCurrentTimestamp(), NULL, false); @@ -5263,7 +5264,7 @@ LockBufferForCleanup(Buffer buffer) if (TimestampDifferenceExceeds(waitStart, now, DeadlockTimeout)) { - LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN, + LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN, waitStart, now, NULL, true); logged_recovery_conflict = true; } @@ -5285,16 +5286,17 @@ LockBufferForCleanup(Buffer buffer) } else { - WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_BUFFER_PIN); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } /* * Remove flag marking us as waiter. Normally this will not be set - * anymore, but ProcWaitForSignal() can return for other signals as - * well. We take care to only reset the flag if we're the waiter, as + * anymore, but WaitInterrupt can return for other reasons as well. + * We take care to only reset the flag if we're the waiter, as * theoretically another backend could have started waiting. That's * impossible with the current usages due to table level locking, but * better be safe. diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c index e4d5b944e12..5b59f773ea4 100644 --- a/src/backend/storage/ipc/ipc.c +++ b/src/backend/storage/ipc/ipc.c @@ -27,6 +27,7 @@ #ifdef PROFILE_PID_DIR #include "postmaster/autovacuum.h" #endif +#include "postmaster/interrupt.h" #include "storage/dsm.h" #include "storage/ipc.h" #include "tcop/tcopprot.h" @@ -175,9 +176,8 @@ proc_exit_prepare(int code) * close up shop already. Note that the signal handlers will not set * these flags again, now that proc_exit_inprogress is set. */ - InterruptPending = false; - ProcDiePending = false; - QueryCancelPending = false; + ClearInterrupt(INTERRUPT_DIE); + ClearInterrupt(INTERRUPT_QUERY_CANCEL); InterruptHoldoffCount = 1; CritSectionCount = 0; diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 2e54c11f880..e70a06e754b 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -58,6 +58,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "port/pg_lfind.h" +#include "postmaster/interrupt.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/acl.h" @@ -3488,13 +3489,13 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid) * Returns pid of the process signaled, or 0 if not found. */ pid_t -CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode) +CancelVirtualTransaction(VirtualTransactionId vxid, InterruptType reason) { - return SignalVirtualTransaction(vxid, sigmode, true); + return SignalVirtualTransaction(vxid, reason, true); } pid_t -SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode, +SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason, bool conflictPending) { ProcArrayStruct *arrayP = procArray; @@ -3522,7 +3523,7 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode, * Kill the pid if it's still here. If not, that's what we * wanted so ignore any errors. */ - (void) SendProcSignal(pid, sigmode, vxid.procNumber); + SendInterrupt(reason, vxid.procNumber); } break; } @@ -3656,7 +3657,7 @@ CountDBConnections(Oid databaseid) * CancelDBBackends --- cancel backends that are using specified database */ void -CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending) +CancelDBBackends(Oid databaseid, InterruptType reason, bool conflictPending) { ProcArrayStruct *arrayP = procArray; int index; @@ -3671,20 +3672,17 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending) if (databaseid == InvalidOid || proc->databaseId == databaseid) { - VirtualTransactionId procvxid; pid_t pid; - GET_VXID_FROM_PGPROC(procvxid, *proc); - proc->recoveryConflictPending = conflictPending; pid = proc->pid; if (pid != 0) { /* - * Kill the pid if it's still here. If not, that's what we - * wanted so ignore any errors. + * Cancel the backend if it's still here. If not, that's what + * we wanted anyway. */ - (void) SendProcSignal(pid, sigmode, procvxid.procNumber); + SendInterrupt(reason, pgprocno); } } } diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c index ddbb041ff9f..d3fdaafc3d6 100644 --- a/src/backend/storage/ipc/procsignal.c +++ b/src/backend/storage/ipc/procsignal.c @@ -27,6 +27,7 @@ #include "replication/walsender.h" #include "storage/condition_variable.h" #include "storage/ipc.h" +#include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinval.h" #include "storage/smgr.h" @@ -34,38 +35,30 @@ #include "utils/memutils.h" /* - * The SIGUSR1 signal is multiplexed to support signaling multiple event - * types. 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 to a process concurrently. (However, if the same - * reason is signaled more than once nearly simultaneously, the process may - * observe it only once.) + * State for the ProcSignalBarrier mechanism and query cancellation. * - * Each process that wants to receive signals registers its process ID - * in the ProcSignalSlots array. The array is indexed by ProcNumber to make - * slot allocation simple, and to avoid having to search the array when you - * know the ProcNumber of the process you're signaling. (We do support - * signaling without ProcNumber, but it's a bit less efficient.) + * Each process that wants to participate in barriers or query cancellation + * registers its process ID in the ProcSignalSlots array. The array is indexed + * by ProcNumber to make slot allocation simple. * * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can * also be read without holding the spinlock, as a quick preliminary check * when searching for a particular PID in the array. * - * pss_signalFlags are intended to be set in cases where we don't need to - * keep track of whether or not the target process has handled the signal, - * but sometimes we need confirmation, as when making a global state change - * that cannot be considered complete until all backends have taken notice - * of it. For such use cases, we set a bit in pss_barrierCheckMask and then + * Plain interrupts are intended to be set in cases where we don't need to + * keep track of whether or not the target process has handled the signal, but + * sometimes we need confirmation, as when making a global state change that + * cannot be considered complete until all backends have taken notice of + * it. For such use cases, we set a bit in pss_barrierCheckMask and then * increment the current "barrier generation"; when the new barrier generation - * (or greater) appears in the pss_barrierGeneration flag of every process, - * we know that the message has been received everywhere. + * (or greater) appears in the pss_barrierGeneration flag of every process, we + * know that the message has been received everywhere. */ typedef struct { pg_atomic_uint32 pss_pid; bool pss_cancel_key_valid; int32 pss_cancel_key; - volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS]; slock_t pss_mutex; /* protects the above fields */ /* Barrier-related fields (not protected by pss_mutex) */ @@ -104,7 +97,6 @@ struct ProcSignalHeader NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL; static ProcSignalSlot *MyProcSignalSlot = NULL; -static bool CheckProcSignal(ProcSignalReason reason); static void CleanupProcSignalState(int status, Datum arg); static void ResetProcSignalBarrierBits(uint32 flags); @@ -150,7 +142,6 @@ ProcSignalShmemInit(void) pg_atomic_init_u32(&slot->pss_pid, 0); slot->pss_cancel_key_valid = false; slot->pss_cancel_key = 0; - MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags)); pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX); pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0); ConditionVariableInit(&slot->pss_barrierCV); @@ -180,9 +171,6 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key) /* Value used for sanity check below */ old_pss_pid = pg_atomic_read_u32(&slot->pss_pid); - /* Clear out any leftover signal reasons */ - MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t)); - /* * Initialize barrier state. Since we're a brand-new process, there * shouldn't be any leftover backend-private state that needs to be @@ -230,9 +218,10 @@ CleanupProcSignalState(int status, Datum arg) ProcSignalSlot *slot = MyProcSignalSlot; /* - * Clear MyProcSignalSlot, so that a SIGUSR1 received after this point - * won't try to access it after it's no longer ours (and perhaps even - * after we've unmapped the shared memory segment). + * Clear MyProcSignalSlot, so that if INTERRUPT_BARRIER is received after + * this point, ProcessProcSignalBarrier() won't try to access it after + * it's no longer ours (and perhaps even after we've unmapped the shared + * memory segment). */ Assert(MyProcSignalSlot != NULL); MyProcSignalSlot = NULL; @@ -268,73 +257,6 @@ CleanupProcSignalState(int status, Datum arg) ConditionVariableBroadcast(&slot->pss_barrierCV); } -/* - * SendProcSignal - * Send a signal to a Postgres process - * - * Providing procNumber is optional, but it will speed up the operation. - * - * On success (a signal was sent), zero is returned. - * On error, -1 is returned, and errno is set (typically to ESRCH or EPERM). - * - * Not to be confused with ProcSendSignal - */ -int -SendProcSignal(pid_t pid, ProcSignalReason reason, ProcNumber procNumber) -{ - volatile ProcSignalSlot *slot; - - if (procNumber != INVALID_PROC_NUMBER) - { - Assert(procNumber < NumProcSignalSlots); - slot = &ProcSignal->psh_slot[procNumber]; - - SpinLockAcquire(&slot->pss_mutex); - if (pg_atomic_read_u32(&slot->pss_pid) == pid) - { - /* Atomically set the proper flag */ - slot->pss_signalFlags[reason] = true; - SpinLockRelease(&slot->pss_mutex); - /* Send signal */ - return kill(pid, SIGUSR1); - } - SpinLockRelease(&slot->pss_mutex); - } - else - { - /* - * procNumber not provided, so search the array using pid. We search - * the array back to front so as to reduce search overhead. Passing - * INVALID_PROC_NUMBER means that the target is most likely an - * auxiliary process, which will have a slot near the end of the - * array. - */ - int i; - - for (i = NumProcSignalSlots - 1; i >= 0; i--) - { - slot = &ProcSignal->psh_slot[i]; - - if (pg_atomic_read_u32(&slot->pss_pid) == pid) - { - SpinLockAcquire(&slot->pss_mutex); - if (pg_atomic_read_u32(&slot->pss_pid) == pid) - { - /* Atomically set the proper flag */ - slot->pss_signalFlags[reason] = true; - SpinLockRelease(&slot->pss_mutex); - /* Send signal */ - return kill(pid, SIGUSR1); - } - SpinLockRelease(&slot->pss_mutex); - } - } - } - - errno = ESRCH; - return -1; -} - /* * EmitProcSignalBarrier * Send a signal to every Postgres process @@ -379,8 +301,8 @@ EmitProcSignalBarrier(ProcSignalBarrierType type) pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1); /* - * Signal all the processes, so that they update their advertised barrier - * generation. + * Send an interrupt to all the processes, so that they update their + * advertised barrier generation. * * Concurrency is not a problem here. Backends that have exited don't * matter, and new backends that have joined since we entered this @@ -391,26 +313,8 @@ EmitProcSignalBarrier(ProcSignalBarrierType type) * backends that need to update state - but they won't actually need to * change any state. */ - for (int i = NumProcSignalSlots - 1; i >= 0; i--) - { - volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i]; - pid_t pid = pg_atomic_read_u32(&slot->pss_pid); - - if (pid != 0) - { - SpinLockAcquire(&slot->pss_mutex); - pid = pg_atomic_read_u32(&slot->pss_pid); - if (pid != 0) - { - /* see SendProcSignal for details */ - slot->pss_signalFlags[PROCSIG_BARRIER] = true; - SpinLockRelease(&slot->pss_mutex); - kill(pid, SIGUSR1); - } - else - SpinLockRelease(&slot->pss_mutex); - } - } + for (ProcNumber pgprocno = 0; pgprocno < NumProcSignalSlots; pgprocno++) + SendInterrupt(INTERRUPT_BARRIER, pgprocno); return generation; } @@ -469,23 +373,6 @@ WaitForProcSignalBarrier(uint64 generation) pg_memory_barrier(); } -/* - * Handle receipt of an interrupt indicating a global barrier event. - * - * All the actual work is deferred to ProcessProcSignalBarrier(), because we - * cannot safely access the barrier generation inside the signal handler as - * 64bit atomics might use spinlock based emulation, even for reads. As this - * routine only gets called when PROCSIG_BARRIER is sent that won't cause a - * lot of unnecessary work. - */ -static void -HandleProcSignalBarrierInterrupt(void) -{ - InterruptPending = true; - ProcSignalBarrierPending = true; - /* interrupt will be raised by procsignal_sigusr1_handler */ -} - /* * Perform global barrier related interrupt checking. * @@ -501,12 +388,12 @@ ProcessProcSignalBarrier(void) uint64 shared_gen; volatile uint32 flags; - Assert(MyProcSignalSlot); - /* Exit quickly if there's no work to do. */ - if (!ProcSignalBarrierPending) + if (!ConsumeInterrupt(INTERRUPT_BARRIER)) + return; + + if (MyProcSignalSlot == NULL) return; - ProcSignalBarrierPending = false; /* * It's not unlikely to process multiple barriers at once, before the @@ -635,86 +522,7 @@ static void ResetProcSignalBarrierBits(uint32 flags) { pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags); - ProcSignalBarrierPending = true; - InterruptPending = true; -} - -/* - * CheckProcSignal - check to see if a particular reason has been - * signaled, and clear the signal flag. Should be called after receiving - * SIGUSR1. - */ -static bool -CheckProcSignal(ProcSignalReason reason) -{ - volatile ProcSignalSlot *slot = MyProcSignalSlot; - - if (slot != NULL) - { - /* - * Careful here --- don't clear flag if we haven't seen it set. - * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to - * read it here safely, without holding the spinlock. - */ - if (slot->pss_signalFlags[reason]) - { - slot->pss_signalFlags[reason] = false; - return true; - } - } - - return false; -} - -/* - * procsignal_sigusr1_handler - handle SIGUSR1 signal. - */ -void -procsignal_sigusr1_handler(SIGNAL_ARGS) -{ - if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT)) - HandleCatchupInterrupt(); - - if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT)) - HandleNotifyInterrupt(); - - if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE)) - HandleParallelMessageInterrupt(); - - if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING)) - HandleWalSndInitStopping(); - - if (CheckProcSignal(PROCSIG_BARRIER)) - HandleProcSignalBarrierInterrupt(); - - if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT)) - HandleLogMemoryContextInterrupt(); - - if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE)) - HandleParallelApplyMessageInterrupt(); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); - - if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN)) - HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); - - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_BARRIER); } /* diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c index 0a9879d7024..29268b88e39 100644 --- a/src/backend/storage/ipc/shm_mq.c +++ b/src/backend/storage/ipc/shm_mq.c @@ -4,9 +4,10 @@ * single-reader, single-writer shared memory message queue * * Both the sender and the receiver must have a PGPROC; their respective - * process interrupts are used for synchronization. Only the sender may send, - * and only the receiver may receive. This is intended to allow a user - * backend to communicate with worker backends that it has registered. + * INTERRUPT_GENERAL interrupts are used for synchronization. Only the + * sender may send, and only the receiver may receive. This is intended to + * allow a user backend to communicate with worker backends that it has + * registered. * * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -49,7 +50,6 @@ * interrupt to the counterparty, the counterparty must be certain to see the * change after waking up. Since SendInterrupt begins with a memory barrier * and ClearInterrupt ends with one, this should be OK. - * XXX: No explicit memory barriers in SendIntterupt/ClearInterrupt, do we need to add some? * * mq_ring_size and mq_ring_offset never change after initialization, and * can therefore be read without the lock. @@ -115,6 +115,7 @@ struct shm_mq * data is 1/4th of the ring size or the tuple queue is full. This will * prevent frequent CPU cache misses, and it will also avoid frequent * SendInterrupt() calls, which are quite expensive. + * XXX: It's not that expensive anymore, if the process isn't currently waiting * * mqh_partial_bytes, mqh_expected_bytes, and mqh_length_word_complete * are used to track the state of non-blocking operations. When the caller @@ -343,16 +344,15 @@ shm_mq_send(shm_mq_handle *mqh, Size nbytes, const void *data, bool nowait, * Write a message into a shared message queue, gathered from multiple * addresses. * - * When nowait = false, we'll wait on our process interrupt when the ring - * buffer fills up, and then continue writing once the receiver has drained - * some data. The process interrupt is cleared after each wait. + * When nowait = false, we'll wait when the ring buffer fills up, and then + * continue writing once the receiver has drained some data. * - * When nowait = true, we do not manipulate the state of the process interrupt; - * instead, if the buffer becomes full, we return SHM_MQ_WOULD_BLOCK. In - * this case, the caller should call this function again, with the same - * arguments, each time the process interrupt is set. (Once begun, the sending - * of a message cannot be aborted except by detaching from the queue; changing - * the length or payload will corrupt the queue.) + * When nowait = true, if the buffer becomes full, we return + * SHM_MQ_WOULD_BLOCK. In this case, the caller should call this function + * again, with the same arguments, each time the INTERRUPT_GENERAL interrupt + * is set. (Once begun, the sending of a message cannot be aborted except by + * detaching from the queue; changing the length or payload will corrupt the + * queue.) * * When force_flush = true, we immediately update the shm_mq's mq_bytes_written * and notify the receiver (if it is already attached). Otherwise, we don't @@ -559,16 +559,15 @@ shm_mq_sendv(shm_mq_handle *mqh, shm_mq_iovec *iov, int iovcnt, bool nowait, * while still allowing longer messages. In either case, the return value * remains valid until the next receive operation is performed on the queue. * - * When nowait = false, we'll wait on our process interrupt when the ring - * buffer is empty and we have not yet received a full message. The sender - * will set our process interrupt after more data has been written, and we'll - * resume processing. Each call will therefore return a complete message - * (unless the sender detaches the queue). + * When nowait = false, we'll when the ring buffer is empty and we have not + * yet received a full message. The sender will send us INTERRUPT_GENERAL + * after more data has been written, and we'll resume processing. Each call + * will therefore return a complete message (unless the sender detaches the + * queue). * - * When nowait = true, we do not manipulate the state of the process - * interrupt; instead, whenever the buffer is empty and we need to read from - * it, we return SHM_MQ_WOULD_BLOCK. In this case, the caller should call - * this function again after the process interrupt has been set. + * When nowait = true, we do not wait when the buffer is empty and return + * SHM_MQ_WOULD_BLOCK instead. In this case, the caller should call this + * function again after INTERRUPT_GENERAL has been set. */ shm_mq_result shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait) @@ -1017,7 +1016,8 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data, * interrupt at top of loop, because setting an already-set * interrupt is much cheaper than setting one that has been reset. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_MESSAGE_QUEUE_SEND); /* Clear the interrupt so we don't spin. */ @@ -1163,14 +1163,15 @@ shm_mq_receive_bytes(shm_mq_handle *mqh, Size bytes_needed, bool nowait, * loop, because setting an already-set interrupt is much cheaper than * setting one that has been cleared. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_MESSAGE_QUEUE_RECEIVE); + /* Handle standard interrupts that may have occurred while we were waiting. */ + CHECK_FOR_INTERRUPTS(); + /* Clear the interrupt so we don't spin. */ ClearInterrupt(INTERRUPT_GENERAL); - - /* An interrupt may have occurred while we were waiting. */ - CHECK_FOR_INTERRUPTS(); } } @@ -1252,14 +1253,15 @@ shm_mq_wait_internal(shm_mq *mq, PGPROC **ptr, BackgroundWorkerHandle *handle) } /* Wait to be signaled. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_MESSAGE_QUEUE_INTERNAL); + /* Handle standard interrupts that may have occurred while we were waiting. */ + CHECK_FOR_INTERRUPTS(); + /* Clear the interrupt so we don't spin. */ ClearInterrupt(INTERRUPT_GENERAL); - - /* An interrupt may have occurred while we were waiting. */ - CHECK_FOR_INTERRUPTS(); } return result; diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c index ab22e0b981d..d3f2647c32d 100644 --- a/src/backend/storage/ipc/signalfuncs.c +++ b/src/backend/storage/ipc/signalfuncs.c @@ -205,7 +205,7 @@ pg_wait_until_termination(int pid, int64 timeout) /* Process interrupts, if any, before waiting */ CHECK_FOR_INTERRUPTS(); - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, waittime, WAIT_EVENT_BACKEND_TERMINATION); diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c index 4a66e1b7f7c..a43541ed72d 100644 --- a/src/backend/storage/ipc/sinval.c +++ b/src/backend/storage/ipc/sinval.c @@ -20,25 +20,8 @@ #include "storage/sinvaladt.h" #include "utils/inval.h" - uint64 SharedInvalidMessageCounter; - -/* - * 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 - * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind. - * - * The signal handler will set an interrupt pending flag and raise the - * INTERRUPT_GENERAL. Whenever starting to read from the client, or when - * interrupted while doing so, ProcessClientReadInterrupt() will call - * ProcessCatchupEvent(). - */ -volatile sig_atomic_t catchupInterruptPending = false; - - /* * SendSharedInvalidMessages * Add shared-cache-invalidation message(s) to the global SI message queue. @@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m * catchup signal this way avoids creating spikes in system load for what * should be just a background maintenance activity. */ - if (catchupInterruptPending) + if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP)) { - catchupInterruptPending = false; elog(DEBUG4, "sinval catchup complete, cleaning queue"); SICleanupQueue(false, 0); } } - -/* - * HandleCatchupInterrupt - * - * This is called when PROCSIG_CATCHUP_INTERRUPT is received. - * - * We used to directly call ProcessCatchupEvent directly when idle. These days - * we just set a flag to do it later and notify the process of that fact by - * raising INTERRUPT_GENERAL. - */ -void -HandleCatchupInterrupt(void) -{ - /* - * Note: this is called by a SIGNAL HANDLER. You must be very wary what - * you do here. - */ - - catchupInterruptPending = true; - - /* make sure the event is processed in due course */ - RaiseInterrupt(INTERRUPT_GENERAL); -} - /* * ProcessCatchupInterrupt * @@ -173,12 +131,12 @@ HandleCatchupInterrupt(void) void ProcessCatchupInterrupt(void) { - while (catchupInterruptPending) + while (IsInterruptPending(INTERRUPT_SINVAL_CATCHUP)) { /* * What we need to do here is cause ReceiveSharedInvalidMessages() to * run, which will do the necessary work and also reset the - * catchupInterruptPending flag. If we are inside a transaction we + * INTERRUPT_SINVAL_CATCHUP flag. If we are inside a transaction we * can just call AcceptInvalidationMessages() to do this. If we * aren't, we start and immediately end a transaction; the call to * AcceptInvalidationMessages() happens down inside transaction start. @@ -190,12 +148,12 @@ ProcessCatchupInterrupt(void) */ if (IsTransactionOrTransactionBlock()) { - elog(DEBUG4, "ProcessCatchupEvent inside transaction"); + elog(DEBUG4, "ProcessCatchupInterrupt inside transaction"); AcceptInvalidationMessages(); } else { - elog(DEBUG4, "ProcessCatchupEvent outside transaction"); + elog(DEBUG4, "ProcessCatchupInterrupt outside transaction"); StartTransactionCommand(); CommitTransactionCommand(); } diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c index 2da91738c32..97ee90c89cf 100644 --- a/src/backend/storage/ipc/sinvaladt.c +++ b/src/backend/storage/ipc/sinvaladt.c @@ -18,10 +18,10 @@ #include #include "miscadmin.h" +#include "postmaster/interrupt.h" #include "storage/ipc.h" #include "storage/proc.h" #include "storage/procnumber.h" -#include "storage/procsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" #include "storage/spin.h" @@ -118,7 +118,7 @@ * we exceed CLEANUP_MIN. Should be a power of 2 for speed. * * SIG_THRESHOLD: the minimum number of messages a backend must have fallen - * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT. + * behind before we'll send it INTERRUPT_SINVAL_CATCHUP. * * WRITE_QUANTUM: the max number of messages to push into the buffer per * iteration of SIInsertDataEntries. Noncritical but should be less than @@ -564,7 +564,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize) * minFree is the minimum number of message slots to make free. * * Possible side effects of this routine include marking one or more - * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT + * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP * to some backend that seems to be getting too far behind. We signal at * most one backend at a time, for reasons explained at the top of the file. * @@ -657,8 +657,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM; /* - * Lastly, signal anyone who needs a catchup interrupt. Since - * SendProcSignal() might not be fast, we don't want to hold locks while + * Lastly, signal anyone who needs a catchup interrupt. SendInterrupt() + * is pretty fast, but we nevertheless don't want to hold locks while * executing it. */ if (needSig) @@ -669,8 +669,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree) needSig->signaled = true; LWLockRelease(SInvalReadLock); LWLockRelease(SInvalWriteLock); - elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid); - SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber); + elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid); + SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber); if (callerHasWriteLock) LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE); } diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index 1ef7600522c..f96c1eeeac3 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -71,13 +71,13 @@ static volatile sig_atomic_t got_standby_delay_timeout = false; static volatile sig_atomic_t got_standby_lock_timeout = false; static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, - ProcSignalReason reason, + InterruptType reason, uint32 wait_event_info, bool report_waiting); -static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason); +static void SendRecoveryConflictWithBufferPin(InterruptType reason); static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts); static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks); -static const char *get_recovery_conflict_desc(ProcSignalReason reason); +static const char *get_recovery_conflict_desc(InterruptType reason); /* * InitRecoveryTransactionEnvironment @@ -271,7 +271,7 @@ WaitExceedsMaxStandbyDelay(uint32 wait_event_info) * to be resolved or not. */ void -LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start, +LogRecoveryConflict(InterruptType reason, TimestampTz wait_start, TimestampTz now, VirtualTransactionId *wait_list, bool still_waiting) { @@ -358,7 +358,7 @@ LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start, */ static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist, - ProcSignalReason reason, uint32 wait_event_info, + InterruptType reason, uint32 wait_event_info, bool report_waiting) { TimestampTz waitStart = 0; @@ -489,7 +489,7 @@ ResolveRecoveryConflictWithSnapshot(TransactionId snapshotConflictHorizon, backends = GetConflictingVirtualXIDs(snapshotConflictHorizon, locator.dbOid); ResolveRecoveryConflictWithVirtualXIDs(backends, - PROCSIG_RECOVERY_CONFLICT_SNAPSHOT, + INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT, WAIT_EVENT_RECOVERY_CONFLICT_SNAPSHOT, true); @@ -560,7 +560,7 @@ ResolveRecoveryConflictWithTablespace(Oid tsid) temp_file_users = GetConflictingVirtualXIDs(InvalidTransactionId, InvalidOid); ResolveRecoveryConflictWithVirtualXIDs(temp_file_users, - PROCSIG_RECOVERY_CONFLICT_TABLESPACE, + INTERRUPT_RECOVERY_CONFLICT_TABLESPACE, WAIT_EVENT_RECOVERY_CONFLICT_TABLESPACE, true); } @@ -581,7 +581,7 @@ ResolveRecoveryConflictWithDatabase(Oid dbid) */ while (CountDBBackends(dbid) > 0) { - CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE, true); + CancelDBBackends(dbid, INTERRUPT_RECOVERY_CONFLICT_DATABASE, true); /* * Wait awhile for them to die so that we avoid flooding an @@ -665,7 +665,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict) * because the caller, WaitOnLock(), has already reported that. */ ResolveRecoveryConflictWithVirtualXIDs(backends, - PROCSIG_RECOVERY_CONFLICT_LOCK, + INTERRUPT_RECOVERY_CONFLICT_LOCK, PG_WAIT_LOCK | locktag.locktag_type, false); } @@ -697,10 +697,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict) } /* Wait to be signaled by the release of the Relation Lock */ - WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, PG_WAIT_LOCK | locktag.locktag_type); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); /* * Exit if ltime is reached. Then all the backends holding conflicting @@ -727,7 +728,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict) while (VirtualTransactionIdIsValid(*backends)) { SignalVirtualTransaction(*backends, - PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK, + INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK, false); backends++; } @@ -749,10 +750,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict) * until the relation locks are released or ltime is reached. */ got_standby_deadlock_timeout = false; - WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, PG_WAIT_LOCK | locktag.locktag_type); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } cleanup: @@ -778,9 +780,10 @@ cleanup: * We either resolve conflicts immediately or set a timeout to wake us at * the limit of our patience. * - * Resolve conflicts by sending a PROCSIG signal to all backends to check if - * they hold one of the buffer pins that is blocking Startup process. If so, - * those backends will take an appropriate error action, ERROR or FATAL. + * Resolve conflicts by sending INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN to all + * backends to check if they hold one of the buffer pins that is blocking + * Startup process. If so, those backends will take an appropriate error + * action, ERROR or FATAL. * * We also must check for deadlocks. Deadlocks occur because if queries * wait on a lock, that must be behind an AccessExclusiveLock, which can only @@ -809,7 +812,7 @@ ResolveRecoveryConflictWithBufferPin(void) /* * We're already behind, so clear a path as quickly as possible. */ - SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); + SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN); } else { @@ -844,17 +847,20 @@ ResolveRecoveryConflictWithBufferPin(void) * We assume that only UnpinBuffer() and the timeout requests established * above can wake us up here. WakeupRecovery() called by walreceiver or * SIGHUP signal handler, etc cannot do that because it uses the different - * interrupt flag. FIXME: seems like a shaky assumption. WakeupRecovery() - * indeed uses a different interrupt flag (different latch earlier), but - * the signal handler?? + * interrupt flag. + * + * FIXME: seems like a shaky assumption. WakeupRecovery() indeed uses a + * different interrupt flag (different latch earlier), but the signal + * handler?? */ - WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_BUFFER_PIN); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); if (got_standby_delay_timeout) - SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN); + SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN); else if (got_standby_deadlock_timeout) { /* @@ -870,7 +876,7 @@ ResolveRecoveryConflictWithBufferPin(void) * not be so harmful because the period that the buffer is kept pinned * is basically no so long. But we should fix this? */ - SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); + SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK); } /* @@ -885,10 +891,10 @@ ResolveRecoveryConflictWithBufferPin(void) } static void -SendRecoveryConflictWithBufferPin(ProcSignalReason reason) +SendRecoveryConflictWithBufferPin(InterruptType reason) { - Assert(reason == PROCSIG_RECOVERY_CONFLICT_BUFFERPIN || - reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK); + Assert(reason == INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN || + reason == INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK); /* * We send signal to all backends to ask them if they are holding the @@ -947,6 +953,7 @@ void StandbyDeadLockHandler(void) { got_standby_deadlock_timeout = true; + RaiseInterrupt(INTERRUPT_GENERAL); } /* @@ -956,6 +963,7 @@ void StandbyTimeoutHandler(void) { got_standby_delay_timeout = true; + RaiseInterrupt(INTERRUPT_GENERAL); } /* @@ -965,6 +973,7 @@ void StandbyLockTimeoutHandler(void) { got_standby_lock_timeout = true; + RaiseInterrupt(INTERRUPT_GENERAL); } /* @@ -1489,31 +1498,31 @@ LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs, /* Return the description of recovery conflict */ static const char * -get_recovery_conflict_desc(ProcSignalReason reason) +get_recovery_conflict_desc(InterruptType reason) { const char *reasonDesc = _("unknown reason"); switch (reason) { - case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: + case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN: reasonDesc = _("recovery conflict on buffer pin"); break; - case PROCSIG_RECOVERY_CONFLICT_LOCK: + case INTERRUPT_RECOVERY_CONFLICT_LOCK: reasonDesc = _("recovery conflict on lock"); break; - case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: + case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE: reasonDesc = _("recovery conflict on tablespace"); break; - case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT: reasonDesc = _("recovery conflict on snapshot"); break; - case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: + case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT: reasonDesc = _("recovery conflict on replication slot"); break; - case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: + case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK: reasonDesc = _("recovery conflict on buffer deadlock"); break; - case PROCSIG_RECOVERY_CONFLICT_DATABASE: + case INTERRUPT_RECOVERY_CONFLICT_DATABASE: reasonDesc = _("recovery conflict on database"); break; default: diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c index 9380dd58d66..23a88316a66 100644 --- a/src/backend/storage/ipc/waiteventset.c +++ b/src/backend/storage/ipc/waiteventset.c @@ -1101,7 +1101,7 @@ WaitEventSetWait(WaitEventSet *set, long timeout, * synchronization so that if an interrupt bit is set after this, * the setter will wake us up. */ - old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << SLEEPING_ON_INTERRUPTS); + old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS); already_pending = ((old_mask & set->interrupt_mask) != 0); /* remember to clear the SLEEPING_ON_INTERRUPTS flag later */ @@ -1176,7 +1176,7 @@ WaitEventSetWait(WaitEventSet *set, long timeout, /* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */ if (sleeping_flag_armed) - pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) 1 << SLEEPING_ON_INTERRUPTS)); + pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS)); #ifndef WIN32 waiting = false; diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c index 295914cf032..4fc4440585b 100644 --- a/src/backend/storage/lmgr/condition_variable.c +++ b/src/backend/storage/lmgr/condition_variable.c @@ -161,7 +161,8 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout, * Wait for interrupt. (If we're awakened for some other reason, the * code below will cope anyway.) */ - (void) WaitInterrupt(INTERRUPT_GENERAL, wait_events, cur_timeout, wait_event_info); + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + wait_events, cur_timeout, wait_event_info); /* Clear the flag before examining the state of the wait list. */ ClearInterrupt(INTERRUPT_GENERAL); diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c index ab9b46b9cb9..54cd99a13ef 100644 --- a/src/backend/storage/lmgr/predicate.c +++ b/src/backend/storage/lmgr/predicate.c @@ -1577,10 +1577,11 @@ GetSafeSnapshot(Snapshot origSnapshot) SxactIsROUnsafe(MySerializableXact))) { LWLockRelease(SerializableXactHashLock); - WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, WAIT_EVENT_SAFE_SNAPSHOT); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE); } MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING; diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 4ebff9a4b37..e4bbcf42261 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -1393,7 +1393,7 @@ ProcSleep(LOCALLOCK *locallock) * because the startup process here has already waited * longer than deadlock_timeout. */ - LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK, + LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_LOCK, standbyWaitStart, now, cnt > 0 ? vxids : NULL, true); logged_recovery_conflict = true; @@ -1402,9 +1402,9 @@ ProcSleep(LOCALLOCK *locallock) } else { - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, PG_WAIT_LOCK | locallock->tag.lock.locktag_type); - ClearInterrupt(INTERRUPT_GENERAL); /* check for deadlocks first, as that's probably log-worthy */ if (got_deadlock_timeout) { @@ -1412,6 +1412,7 @@ ProcSleep(LOCALLOCK *locallock) got_deadlock_timeout = false; } CHECK_FOR_INTERRUPTS(); + ClearInterrupt(INTERRUPT_GENERAL); } /* @@ -1658,7 +1659,7 @@ ProcSleep(LOCALLOCK *locallock) /* * Disable the timers, if they are still running. As in LockErrorCleanup, * we must preserve the LOCK_TIMEOUT indicator flag: if a lock timeout has - * already caused QueryCancelPending to become set, we want the cancel to + * already raised INTERRUPT_QUERY_CANCEL, we want the cancel to * be reported as a lock timeout, not a user cancel. */ if (!InHotStandby) @@ -1682,7 +1683,7 @@ ProcSleep(LOCALLOCK *locallock) * startup process waited longer than deadlock_timeout for it. */ if (InHotStandby && logged_recovery_conflict) - LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK, + LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_LOCK, standbyWaitStart, GetCurrentTimestamp(), NULL, false); @@ -1881,10 +1882,6 @@ CheckDeadLockAlert(void) * did. Back then got_deadlock_timeout wasn't yet set... It's unlikely * that this ever would be a problem, but raising an interrupt again is * cheap. - * - * Note that, when this function runs inside procsignal_sigusr1_handler(), - * the handler function raises the interrupt again after the interrupt is - * raised here. */ RaiseInterrupt(INTERRUPT_GENERAL); errno = save_errno; diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 8784ea78dcd..cc19c0157ec 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -66,6 +66,7 @@ #include "storage/proc.h" #include "storage/procsignal.h" #include "storage/sinval.h" +#include "storage/standby.h" #include "tcop/fastpath.h" #include "tcop/pquery.h" #include "tcop/tcopprot.h" @@ -153,10 +154,6 @@ static const char *userDoption = NULL; /* -D switch */ static bool EchoQuery = false; /* -E switch */ static bool UseSemiNewlineNewline = false; /* -j switch */ -/* whether or not, and why, we were canceled by conflict with recovery */ -static volatile sig_atomic_t RecoveryConflictPending = false; -static volatile sig_atomic_t RecoveryConflictPendingReasons[NUM_PROCSIGNALS]; - /* reused buffer to pass to SendRowDescriptionMessage() */ static MemoryContext row_description_context = NULL; static StringInfoData row_description_buf; @@ -495,42 +492,71 @@ ReadCommand(StringInfo inBuf) * false if about to read or done reading. * * Must preserve errno! + * + * Returns an interrupt mask with the bits set that a new call to this + * function with blocked==true would handle and clear in the current + * state. The caller can use the returned mask in a WaitInterrupt() call, to + * wait for interrupts that this would handle, and + * ProcessClientReadInterrupt() promises to clear those bits if called again. */ -void +uint32 ProcessClientReadInterrupt(bool blocked) { int save_errno = errno; + uint32 interruptMask = 0; if (DoingCommandRead) { - /* Check for general interrupts that arrived before/while reading */ + /* Check for standard interrupts that arrived before/while reading */ + interruptMask |= INTERRUPT_CFI_MASK(); CHECK_FOR_INTERRUPTS(); /* Process sinval catchup interrupts, if any */ - if (catchupInterruptPending) + interruptMask |= INTERRUPT_SINVAL_CATCHUP; + if (IsInterruptPending(INTERRUPT_SINVAL_CATCHUP)) ProcessCatchupInterrupt(); - /* Process notify interrupts, if any */ - if (notifyInterruptPending) - ProcessNotifyInterrupt(true); - } - else if (ProcDiePending) - { /* - * We're dying. If there is no data available to read, then it's safe - * (and sane) to handle that now. If we haven't tried to read yet, - * make sure the interrupt flag is set, so that if there is no data - * then we'll come back here and die. If we're done reading, also - * make sure the interrupt flag is set, as we might've undesirably - * cleared it while reading. + * If we are truly idle, ie. *not* inside a transaction block matching + * the conditions in PostgresMain(), there are a few additional + * interrupts that we can process immediately. */ - if (blocked) - CHECK_FOR_INTERRUPTS(); - else - RaiseInterrupt(INTERRUPT_GENERAL); + if (!IsTransactionOrTransactionBlock()) + { + interruptMask |= INTERRUPT_ASYNC_NOTIFY; + if (IsInterruptPending(INTERRUPT_ASYNC_NOTIFY)) + ProcessNotifyInterrupt(true); + + interruptMask |= INTERRUPT_IDLE_STATS_TIMEOUT; + if (IsInterruptPending(INTERRUPT_IDLE_STATS_TIMEOUT)) + { + ClearInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT); + pgstat_report_stat(true); + } + } + } + else if ((INTERRUPT_CFI_MASK() & INTERRUPT_DIE) != 0) + { + interruptMask |= INTERRUPT_DIE; + if (IsInterruptPending(INTERRUPT_DIE)) + { + /* + * We're dying. If there is no data available to read, then it's safe + * (and sane) to handle that now. If we haven't tried to read yet, + * don't clear the interrupt flag is set, so that if there is no data + * then we'll come back here and die. + */ + if (blocked) + { + CHECK_FOR_INTERRUPTS(); + Assert(false); /* CHECK_FOR_INTERRUPTS should've bailed out */ + } + } } errno = save_errno; + + return interruptMask; } /* @@ -541,30 +567,30 @@ ProcessClientReadInterrupt(bool blocked) * false if about to write or done writing. * * Must preserve errno! + * + * Like ProcessClientReadInterrupt, returns an interrupt mask with the bits + * set that a new call to this function with blocked==true would handle and + * clear in the current state. */ -void +uint32 ProcessClientWriteInterrupt(bool blocked) { int save_errno = errno; + uint32 interruptMask = 0; - if (ProcDiePending) + if ((INTERRUPT_CFI_MASK() & INTERRUPT_DIE) != 0) { - /* - * We're dying. If it's not possible to write, then we should handle - * that immediately, else a stuck client could indefinitely delay our - * response to the signal. If we haven't tried to write yet, make - * sure the interrupt flag is set, so that if the write would block - * then we'll come back here and die. If we're done writing, also - * make sure the interrupt flag is set, as we might've undesirably - * cleared it while writing. - */ - if (blocked) + interruptMask |= INTERRUPT_DIE; + if (IsInterruptPending(INTERRUPT_DIE)) { /* - * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't - * service ProcDiePending. + * We're dying. If it's not possible to write, then we should + * handle that immediately, else a stuck client could indefinitely + * delay our response to the signal. If we haven't tried to write + * yet, make sure to not clear the interrupt flag set, so that if + * the write would block then we'll come back here and die. */ - if (InterruptHoldoffCount == 0 && CritSectionCount == 0) + if (blocked) { /* * We don't want to send the client the error message, as a) @@ -576,13 +602,14 @@ ProcessClientWriteInterrupt(bool blocked) whereToSendOutput = DestNone; CHECK_FOR_INTERRUPTS(); + Assert(false); /* CHECK_FOR_INTERRUPTS should've bailed out */ } } - else - RaiseInterrupt(INTERRUPT_GENERAL); } errno = save_errno; + + return interruptMask; } /* @@ -2527,29 +2554,29 @@ errdetail_abort(void) * Add an errdetail() line showing conflict source. */ static int -errdetail_recovery_conflict(ProcSignalReason reason) +errdetail_recovery_conflict(InterruptType reason) { switch (reason) { - case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: + case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN: errdetail("User was holding shared buffer pin for too long."); break; - case PROCSIG_RECOVERY_CONFLICT_LOCK: + case INTERRUPT_RECOVERY_CONFLICT_LOCK: errdetail("User was holding a relation lock for too long."); break; - case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: + case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE: errdetail("User was or might have been using tablespace that must be dropped."); break; - case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT: errdetail("User query might have needed to see row versions that must be removed."); break; - case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: + case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT: errdetail("User was using a logical replication slot that must be invalidated."); break; - case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: + case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK: errdetail("User transaction caused buffer deadlock with recovery."); break; - case PROCSIG_RECOVERY_CONFLICT_DATABASE: + case INTERRUPT_RECOVERY_CONFLICT_DATABASE: errdetail("User was connected to a database that must be dropped."); break; default: @@ -3004,17 +3031,11 @@ die(SIGNAL_ARGS) { /* Don't joggle the elbow of proc_exit */ if (!proc_exit_inprogress) - { - InterruptPending = true; - ProcDiePending = true; - } + RaiseInterrupt(INTERRUPT_DIE); /* for the cumulative stats system */ pgStatSessionEndCause = DISCONNECT_KILLED; - /* If we're still here, waken anything waiting on the interrupt */ - RaiseInterrupt(INTERRUPT_GENERAL); - /* * If we're in single user mode, we want to quit immediately - we can't * rely on interrupts as they wouldn't work when stdin/stdout is a file. @@ -3032,17 +3053,7 @@ die(SIGNAL_ARGS) void StatementCancelHandler(SIGNAL_ARGS) { - /* - * Don't joggle the elbow of proc_exit - */ - if (!proc_exit_inprogress) - { - InterruptPending = true; - QueryCancelPending = true; - } - - /* If we're still here, waken anything waiting on the interrupt */ - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_QUERY_CANCEL); } /* signal handler for floating point exception */ @@ -3059,27 +3070,14 @@ FloatExceptionHandler(SIGNAL_ARGS) } /* - * Tell the next CHECK_FOR_INTERRUPTS() to check for a particular type of - * recovery conflict. Runs in a SIGUSR1 handler. - */ -void -HandleRecoveryConflictInterrupt(ProcSignalReason reason) -{ - RecoveryConflictPendingReasons[reason] = true; - RecoveryConflictPending = true; - InterruptPending = true; - /* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */ -} - -/* - * Check one individual conflict reason. + * Process one individual conflict reason. */ static void -ProcessRecoveryConflictInterrupt(ProcSignalReason reason) +ProcessRecoveryConflictInterrupt(InterruptType reason) { switch (reason) { - case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: + case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK: /* * If we aren't waiting for a lock we can never deadlock. @@ -3090,21 +3088,21 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) /* Intentional fall through to check wait for pin */ /* FALLTHROUGH */ - case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: + case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN: /* - * If PROCSIG_RECOVERY_CONFLICT_BUFFERPIN is requested but we + * If INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN is requested but we * aren't blocking the Startup process there is nothing more to * do. * - * When PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested, + * When INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested, * if we're waiting for locks and the startup process is not * waiting for buffer pin (i.e., also waiting for locks), we set * the flag so that ProcSleep() will check for deadlocks. */ if (!HoldingBufferPinThatDelaysRecovery()) { - if (reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK && + if (reason == INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK && GetStartupBufferPinWaitBufId() < 0) CheckDeadLockAlert(); return; @@ -3115,9 +3113,9 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) /* Intentional fall through to error handling */ /* FALLTHROUGH */ - case PROCSIG_RECOVERY_CONFLICT_LOCK: - case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: - case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + case INTERRUPT_RECOVERY_CONFLICT_LOCK: + case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE: + case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT: /* * If we aren't in a transaction any longer then ignore. @@ -3127,14 +3125,14 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) /* FALLTHROUGH */ - case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: + case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT: /* * If we're not in a subtransaction then we are OK to throw an * ERROR to resolve the conflict. Otherwise drop through to the * FATAL case. * - * PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT is a special case that + * RECOVERY_CONFLICT_LOGICALSLOT is a special case that * always throws an ERROR (ie never promotes to FATAL), though it * still has to respect QueryCancelHoldoffCount, so it shares this * code path. Logical decoding slots are only acquired while @@ -3144,17 +3142,17 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) * intercept an error before the replication slot is released. * * XXX other times that we can throw just an ERROR *may* be - * PROCSIG_RECOVERY_CONFLICT_LOCK if no locks are held in parent + * RECOVERY_CONFLICT_LOCK if no locks are held in parent * transactions * - * PROCSIG_RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by + * RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by * parent transactions and the transaction is not * transaction-snapshot mode * - * PROCSIG_RECOVERY_CONFLICT_TABLESPACE if no temp files or + * RECOVERY_CONFLICT_TABLESPACE if no temp files or * cursors open in parent transactions */ - if (reason == PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT || + if (reason == INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT || !IsSubTransaction()) { /* @@ -3178,12 +3176,10 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) if (QueryCancelHoldoffCount != 0) { /* - * Re-arm and defer this interrupt until later. See - * similar code in ProcessInterrupts(). + * Leave the interrupt set to defer this interrupt + * until later. See similar code in + * ProcessInterrupts(). */ - RecoveryConflictPendingReasons[reason] = true; - RecoveryConflictPending = true; - InterruptPending = true; return; } @@ -3206,7 +3202,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) /* Intentional fall through to session cancel */ /* FALLTHROUGH */ - case PROCSIG_RECOVERY_CONFLICT_DATABASE: + case INTERRUPT_RECOVERY_CONFLICT_DATABASE: /* * Retrying is not possible because the database is dropped, or we @@ -3215,7 +3211,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) */ pgstat_report_recovery_conflict(reason); ereport(FATAL, - (errcode(reason == PROCSIG_RECOVERY_CONFLICT_DATABASE ? + (errcode(reason == INTERRUPT_RECOVERY_CONFLICT_DATABASE ? ERRCODE_DATABASE_DROPPED : ERRCODE_T_R_SERIALIZATION_FAILURE), errmsg("terminating connection due to conflict with recovery"), @@ -3229,47 +3225,17 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason) } } -/* - * Check each possible recovery conflict reason. - */ -static void -ProcessRecoveryConflictInterrupts(void) -{ - /* - * We don't need to worry about joggling the elbow of proc_exit, because - * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call - * us. - */ - Assert(!proc_exit_inprogress); - Assert(InterruptHoldoffCount == 0); - Assert(RecoveryConflictPending); - - RecoveryConflictPending = false; - - for (ProcSignalReason reason = PROCSIG_RECOVERY_CONFLICT_FIRST; - reason <= PROCSIG_RECOVERY_CONFLICT_LAST; - reason++) - { - if (RecoveryConflictPendingReasons[reason]) - { - RecoveryConflictPendingReasons[reason] = false; - ProcessRecoveryConflictInterrupt(reason); - } - } -} - /* * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro * * If an interrupt condition is pending, and it's safe to service it, - * then clear the flag and accept the interrupt. Called only when - * InterruptPending is true. + * then clear the flag and accept the interrupt. * - * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts - * is guaranteed to clear the InterruptPending flag before returning. - * (This is not the same as guaranteeing that it's still clear when we - * return; another interrupt could have arrived. But we promise that - * any pre-existing one will have been serviced.) + * Note: ProcessInterrupts is guaranteed to clear the interrupt bits in + * INTERRUPT_CFI_MASK() before returning. (This is not the same as + * guaranteeing that it's still clear when we return; another interrupt could + * have arrived. But we promise that any pre-existing one will have been + * serviced.) */ void ProcessInterrupts(void) @@ -3277,12 +3243,18 @@ ProcessInterrupts(void) /* OK to accept any interrupts now? */ if (InterruptHoldoffCount != 0 || CritSectionCount != 0) return; - InterruptPending = false; - if (ProcDiePending) + /* + * TODO: it'd be good for performance to read the atomic + * MyPendingInterrupts variable just once in this function + */ + + if (ConsumeInterrupt(INTERRUPT_WALSND_INIT_STOPPING)) + ProcessWalSndInitStopping(); + + if (ConsumeInterrupt(INTERRUPT_DIE)) { - ProcDiePending = false; - QueryCancelPending = false; /* ProcDie trumps QueryCancel */ + ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* ProcDie trumps QueryCancel */ LockErrorCleanup(); /* As in quickdie, don't risk sending to client during auth */ if (ClientAuthInProgress && whereToSendOutput == DestRemote) @@ -3321,10 +3293,8 @@ ProcessInterrupts(void) errmsg("terminating connection due to administrator command"))); } - if (CheckClientConnectionPending) + if (ConsumeInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT)) { - CheckClientConnectionPending = false; - /* * Check for lost connection and re-arm, if still configured, but not * if we've arrived back at DoingCommandRead state. We don't want to @@ -3334,16 +3304,16 @@ ProcessInterrupts(void) if (!DoingCommandRead && client_connection_check_interval > 0) { if (!pq_check_connection()) - ClientConnectionLost = true; + RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST); else enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT, client_connection_check_interval); } } - if (ClientConnectionLost) + if (IsInterruptPending(INTERRUPT_CLIENT_CONNECTION_LOST)) { - QueryCancelPending = false; /* lost connection trumps QueryCancel */ + ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps QueryCancel */ LockErrorCleanup(); /* don't send to client, we already know the connection to be dead. */ whereToSendOutput = DestNone; @@ -3360,25 +3330,11 @@ ProcessInterrupts(void) * * See similar logic in ProcessRecoveryConflictInterrupts(). */ - if (QueryCancelPending && QueryCancelHoldoffCount != 0) - { - /* - * Re-arm InterruptPending so that we process the cancel request as - * soon as we're done reading the message. (XXX this is seriously - * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we - * can't use that macro directly as the initial test in this function, - * meaning that this code also creates opportunities for other bugs to - * appear.) - */ - InterruptPending = true; - } - else if (QueryCancelPending) + if (QueryCancelHoldoffCount == 0 && ConsumeInterrupt(INTERRUPT_QUERY_CANCEL)) { bool lock_timeout_occurred; bool stmt_timeout_occurred; - QueryCancelPending = false; - /* * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we * need to clear both, so always fetch both. @@ -3432,10 +3388,28 @@ ProcessInterrupts(void) } } - if (RecoveryConflictPending) - ProcessRecoveryConflictInterrupts(); + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_DATABASE)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_DATABASE); + + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_TABLESPACE)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_TABLESPACE); + + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOCK)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOCK); - if (IdleInTransactionSessionTimeoutPending) + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT); + + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT); + + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN); + + if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK)) + ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK); + + if (ConsumeInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT)) { /* * If the GUC has been reset to zero, ignore the signal. This is @@ -3443,7 +3417,6 @@ ProcessInterrupts(void) * interrupt. We need to unset the flag before the injection point, * otherwise we could loop in interrupts checking. */ - IdleInTransactionSessionTimeoutPending = false; if (IdleInTransactionSessionTimeout > 0) { INJECTION_POINT("idle-in-transaction-session-timeout"); @@ -3453,10 +3426,9 @@ ProcessInterrupts(void) } } - if (TransactionTimeoutPending) + if (ConsumeInterrupt(INTERRUPT_TRANSACTION_TIMEOUT)) { /* As above, ignore the signal if the GUC has been reset to zero. */ - TransactionTimeoutPending = false; if (TransactionTimeout > 0) { INJECTION_POINT("transaction-timeout"); @@ -3466,10 +3438,9 @@ ProcessInterrupts(void) } } - if (IdleSessionTimeoutPending) + if (ConsumeInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT)) { /* As above, ignore the signal if the GUC has been reset to zero. */ - IdleSessionTimeoutPending = false; if (IdleSessionTimeout > 0) { INJECTION_POINT("idle-session-timeout"); @@ -3479,28 +3450,17 @@ ProcessInterrupts(void) } } - /* - * If there are pending stats updates and we currently are truly idle - * (matching the conditions in PostgresMain(), report stats now. - */ - if (IdleStatsUpdateTimeoutPending && - DoingCommandRead && !IsTransactionOrTransactionBlock()) - { - IdleStatsUpdateTimeoutPending = false; - pgstat_report_stat(true); - } - - if (ProcSignalBarrierPending) + if (IsInterruptPending(INTERRUPT_BARRIER)) ProcessProcSignalBarrier(); - if (ParallelMessagePending) + if (ConsumeInterrupt(INTERRUPT_PARALLEL_MESSAGE)) ProcessParallelMessages(); - if (LogMemoryContextPending) - ProcessLogMemoryContextInterrupt(); - - if (ParallelApplyMessagePending) + if (ConsumeInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE)) ProcessParallelApplyMessages(); + + if (ConsumeInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT)) + ProcessLogMemoryContextInterrupt(); } /* @@ -4203,7 +4163,7 @@ PostgresMain(const char *dbname, const char *username) * midst of output during who-knows-what operation... */ pqsignal(SIGPIPE, SIG_IGN); - pqsignal(SIGUSR1, procsignal_sigusr1_handler); + pqsignal(SIGUSR1, SIG_IGN); pqsignal(SIGUSR2, SIG_IGN); pqsignal(SIGFPE, FloatExceptionHandler); @@ -4377,7 +4337,7 @@ PostgresMain(const char *dbname, const char *username) * forgetting a timeout cancel. */ disable_all_timeouts(false); /* do first to avoid race condition */ - QueryCancelPending = false; + ClearInterrupt(INTERRUPT_QUERY_CANCEL); idle_in_transaction_timeout_enabled = false; idle_session_timeout_enabled = false; @@ -4562,7 +4522,7 @@ PostgresMain(const char *dbname, const char *username) * were received during the just-finished transaction, they'll * be seen by the client before ReadyForQuery is. */ - if (notifyInterruptPending) + if (IsInterruptPending(INTERRUPT_ASYNC_NOTIFY)) ProcessNotifyInterrupt(false); /* @@ -4648,7 +4608,7 @@ PostgresMain(const char *dbname, const char *username) * * Query cancel is supposed to be a no-op when there is no query in * progress, so if a query cancel arrived while we were idle, just - * reset QueryCancelPending. ProcessInterrupts() has that effect when + * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect when * it's called when DoingCommandRead is set, so check for interrupts * before resetting DoingCommandRead. */ @@ -4659,11 +4619,8 @@ PostgresMain(const char *dbname, const char *username) * (6) check for any other interesting events that happened while we * slept. */ - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } /* * (7) process the command. But ignore it if we're skipping till diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c index 05a8ccfdb75..762ded643b9 100644 --- a/src/backend/utils/activity/pgstat_database.c +++ b/src/backend/utils/activity/pgstat_database.c @@ -17,7 +17,7 @@ #include "postgres.h" -#include "storage/procsignal.h" +#include "postmaster/interrupt.h" #include "utils/pgstat_internal.h" #include "utils/timestamp.h" @@ -78,7 +78,7 @@ pgstat_report_autovac(Oid dboid) * Report a Hot Standby recovery conflict. */ void -pgstat_report_recovery_conflict(int reason) +pgstat_report_recovery_conflict(InterruptType reason) { PgStat_StatDBEntry *dbentry; @@ -90,31 +90,33 @@ pgstat_report_recovery_conflict(int reason) switch (reason) { - case PROCSIG_RECOVERY_CONFLICT_DATABASE: + case INTERRUPT_RECOVERY_CONFLICT_DATABASE: /* * Since we drop the information about the database as soon as it * replicates, there is no point in counting these conflicts. */ break; - case PROCSIG_RECOVERY_CONFLICT_TABLESPACE: + case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE: dbentry->conflict_tablespace++; break; - case PROCSIG_RECOVERY_CONFLICT_LOCK: + case INTERRUPT_RECOVERY_CONFLICT_LOCK: dbentry->conflict_lock++; break; - case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT: + case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT: dbentry->conflict_snapshot++; break; - case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN: + case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN: dbentry->conflict_bufferpin++; break; - case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT: + case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT: dbentry->conflict_logicalslot++; break; - case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK: + case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK: dbentry->conflict_startup_deadlock++; break; + default: + elog(LOG, "unexpected recovery conflict reason %u", (unsigned int) reason); } } diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c index 396c2f223b4..a08fcffbc0f 100644 --- a/src/backend/utils/adt/mcxtfuncs.c +++ b/src/backend/utils/adt/mcxtfuncs.c @@ -265,7 +265,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS) { int pid = PG_GETARG_INT32(0); PGPROC *proc; - ProcNumber procNumber = INVALID_PROC_NUMBER; /* * See if the process with given pid is a backend or an auxiliary process. @@ -294,14 +293,13 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS) PG_RETURN_BOOL(false); } - procNumber = GetNumberFromPGProc(proc); - if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0) - { - /* Again, just a warning to allow loops */ - ereport(WARNING, - (errmsg("could not send signal to process %d: %m", pid))); - PG_RETURN_BOOL(false); - } + /* + * XXX: We're not holding any locks here, so the PGPROC could get recycled + * for another backend. If that happens, we'll print the memory dump of + * the newly started process instead, which is confusing but otherwise + * harmless. + */ + SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc)); PG_RETURN_BOOL(true); } diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index d7e9da57bd1..6b410fbbc9f 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -403,11 +403,10 @@ pg_sleep(PG_FUNCTION_ARGS) else break; - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK(), WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, delay_ms, WAIT_EVENT_PG_SLEEP); - ClearInterrupt(INTERRUPT_GENERAL); } PG_RETURN_VOID(); diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c index 79bc999cf26..100a9e119f1 100644 --- a/src/backend/utils/init/globals.c +++ b/src/backend/utils/init/globals.c @@ -28,21 +28,6 @@ ProtocolVersion FrontendProtocol; -volatile sig_atomic_t InterruptPending = false; -volatile sig_atomic_t QueryCancelPending = false; -volatile sig_atomic_t ProcDiePending = false; -volatile sig_atomic_t CheckClientConnectionPending = false; -volatile sig_atomic_t ClientConnectionLost = false; -volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false; -volatile sig_atomic_t TransactionTimeoutPending = false; -volatile sig_atomic_t IdleSessionTimeoutPending = false; -volatile sig_atomic_t ProcSignalBarrierPending = false; -volatile sig_atomic_t LogMemoryContextPending = false; -volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false; -volatile uint32 InterruptHoldoffCount = 0; -volatile uint32 QueryCancelHoldoffCount = 0; -volatile uint32 CritSectionCount = 0; - int MyProcPid; pg_time_t MyStartTime; TimestampTz MyStartTimestamp; diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index e11101a36b0..5f539157fea 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -1382,41 +1382,31 @@ LockTimeoutHandler(void) static void TransactionTimeoutHandler(void) { - TransactionTimeoutPending = true; - InterruptPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT); } static void IdleInTransactionSessionTimeoutHandler(void) { - IdleInTransactionSessionTimeoutPending = true; - InterruptPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT); } static void IdleSessionTimeoutHandler(void) { - IdleSessionTimeoutPending = true; - InterruptPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT); } static void IdleStatsUpdateTimeoutHandler(void) { - IdleStatsUpdateTimeoutPending = true; - InterruptPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT); } static void ClientCheckTimeoutHandler(void) { - CheckClientConnectionPending = true; - InterruptPending = true; - RaiseInterrupt(INTERRUPT_GENERAL); + RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT); } /* diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index e90d82c7ddc..b63613118b7 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -370,12 +370,6 @@ handle_sig_alarm(SIGNAL_ARGS) */ HOLD_INTERRUPTS(); - /* - * SIGALRM is always cause for waking anything waiting on - * INTERRUPT_GENERAL. - */ - RaiseInterrupt(INTERRUPT_GENERAL); - /* * Always reset signal_pending, even if !alarm_enabled, since indeed no * signal is now pending. diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c index 5233a65aaa0..63cee06a12a 100644 --- a/src/backend/utils/mmgr/mcxt.c +++ b/src/backend/utils/mmgr/mcxt.c @@ -1260,27 +1260,11 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags) return ret; } -/* - * HandleLogMemoryContextInterrupt - * Handle receipt of an interrupt indicating logging of memory - * contexts. - * - * All the actual work is deferred to ProcessLogMemoryContextInterrupt(), - * because we cannot safely emit a log message inside the signal handler. - */ -void -HandleLogMemoryContextInterrupt(void) -{ - InterruptPending = true; - LogMemoryContextPending = true; - /* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */ -} - /* * ProcessLogMemoryContextInterrupt * Perform logging of memory contexts of this backend process. * - * Any backend that participates in ProcSignal signaling must arrange + * Any backend that participates in standard interrupt handling must arrange * to call this function if we see LogMemoryContextPending set. * It is called from CHECK_FOR_INTERRUPTS(), which is enough because * the target process for logging of memory contexts is a backend. @@ -1288,7 +1272,7 @@ HandleLogMemoryContextInterrupt(void) void ProcessLogMemoryContextInterrupt(void) { - LogMemoryContextPending = false; + ClearInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT); /* * Use LOG_SERVER_ONLY to prevent this message from being sent to the diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h index a257d0a8ee6..b7bdada3646 100644 --- a/src/include/access/parallel.h +++ b/src/include/access/parallel.h @@ -14,8 +14,6 @@ #ifndef PARALLEL_H #define PARALLEL_H -#include - #include "access/xlogdefs.h" #include "lib/ilist.h" #include "postmaster/bgworker.h" @@ -55,7 +53,6 @@ typedef struct ParallelWorkerContext shm_toc *toc; } ParallelWorkerContext; -extern PGDLLIMPORT volatile sig_atomic_t ParallelMessagePending; extern PGDLLIMPORT int ParallelWorkerNumber; extern PGDLLIMPORT bool InitializingParallelWorker; @@ -72,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt); extern void DestroyParallelContext(ParallelContext *pcxt); extern bool ParallelContextActive(void); -extern void HandleParallelMessageInterrupt(void); extern void ProcessParallelMessages(void); extern void AtEOXact_Parallel(bool isCommit); extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId); diff --git a/src/include/commands/async.h b/src/include/commands/async.h index f75c3df9556..50773ddb0e2 100644 --- a/src/include/commands/async.h +++ b/src/include/commands/async.h @@ -13,11 +13,8 @@ #ifndef ASYNC_H #define ASYNC_H -#include - extern PGDLLIMPORT bool Trace_notify; extern PGDLLIMPORT int max_notify_queue_pages; -extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending; extern Size AsyncShmemSize(void); extern void AsyncShmemInit(void); @@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void); extern void AtSubAbort_Notify(void); extern void AtPrepare_Notify(void); -/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */ -extern void HandleNotifyInterrupt(void); - /* process interrupts */ extern void ProcessNotifyInterrupt(bool flush); diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h index dd361be4afd..310c45f689c 100644 --- a/src/include/libpq/libpq-be-fe-helpers.h +++ b/src/include/libpq/libpq-be-fe-helpers.h @@ -209,7 +209,7 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info) else io_flag = WL_SOCKET_WRITEABLE; - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK(), WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag, PQsocket(conn), 0, @@ -217,10 +217,7 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info) /* Interrupted? */ if (rc & WL_INTERRUPT) - { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); - } /* If socket is ready, advance the libpq state machine */ if (rc & io_flag) @@ -341,7 +338,7 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info) { int rc; - rc = WaitInterruptOrSocket(INTERRUPT_GENERAL, + rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK(), WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | WL_SOCKET_READABLE, PQsocket(conn), @@ -350,10 +347,7 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info) /* Interrupted? */ if (rc & WL_INTERRUPT) - { - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); - } /* Consume whatever data is available from the socket */ if (PQconsumeInput(conn) == 0) @@ -436,12 +430,10 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime) } /* Sleep until there's something to do */ - WaitInterruptOrSocket(INTERRUPT_GENERAL, waitEvents, + WaitInterruptOrSocket(INTERRUPT_CFI_MASK(), waitEvents, PQcancelSocket(cancel_conn), cur_timeout, PG_WAIT_CLIENT); - ClearInterrupt(INTERRUPT_GENERAL); - CHECK_FOR_INTERRUPTS(); } exit: ; diff --git a/src/include/libpq/pqmq.h b/src/include/libpq/pqmq.h index 348b4494333..ae02aacfaa1 100644 --- a/src/include/libpq/pqmq.h +++ b/src/include/libpq/pqmq.h @@ -17,7 +17,7 @@ #include "storage/shm_mq.h" extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh); -extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber); +extern void pq_set_parallel_leader(ProcNumber procNumber); extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata); diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h index f5b773b79e5..08de6146800 100644 --- a/src/include/miscadmin.h +++ b/src/include/miscadmin.h @@ -28,124 +28,28 @@ #include "datatype/timestamp.h" /* for TimestampTz */ #include "pgtime.h" /* for pg_time_t */ +/* + * for backwards-compatibility: CHECK_FOR_INTERRUPTS() used to be here, and it's + * used all over the place + */ +#ifndef FRONTEND +#include "postmaster/interrupt.h" +#endif #define InvalidPid (-1) /***************************************************************************** - * System interrupt and critical section handling - * - * There are two types of interrupts that a running backend needs to accept - * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM). - * In both cases, we need to be able to clean up the current transaction - * gracefully, so we can't respond to the interrupt instantaneously --- - * there's no guarantee that internal data structures would be self-consistent - * if the code is interrupted at an arbitrary instant. Instead, the signal - * handlers set flags that are checked periodically during execution. - * - * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots - * where it is normally safe to accept a cancel or die interrupt. In some - * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that - * might sometimes be called in contexts that do *not* want to allow a cancel - * or die interrupt. The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros - * allow code to ensure that no cancel or die interrupt will be accepted, - * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine. The interrupt - * will be held off until CHECK_FOR_INTERRUPTS() is done outside any - * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section. - * - * There is also a mechanism to prevent query cancel interrupts, while still - * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and - * RESUME_CANCEL_INTERRUPTS(). + * Critical section handling * - * Note that ProcessInterrupts() has also acquired a number of tasks that - * do not necessarily cause a query-cancel-or-die response. Hence, it's - * possible that it will just clear InterruptPending and return. - * - * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an - * interrupt needs to be serviced, without trying to do so immediately. - * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(), - * which tells whether ProcessInterrupts is sure to clear the interrupt. - * - * Special mechanisms are used to let an interrupt be accepted when we are - * waiting for a lock or when we are waiting for command input (but, of - * course, only if the interrupt holdoff counter is zero). See the - * related code for details. - * - * A lost connection is handled similarly, although the loss of connection - * does not raise a signal, but is detected when we fail to write to the - * socket. If there was a signal for a broken connection, we could make use of - * it by setting ClientConnectionLost in the signal handler. - * - * A related, but conceptually distinct, mechanism is the "critical section" - * mechanism. A critical section not only holds off cancel/die interrupts, - * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC) - * --- that is, a system-wide reset is forced. Needless to say, only really - * *critical* code should be marked as a critical section! Currently, this - * mechanism is only used for XLOG-related code. + * A critical section holds off cancel/die interrupts like HOLD_INTERRUPTS() + * does, and also causes any ereport(ERROR) or ereport(FATAL) to become + * ereport(PANIC) --- that is, a system-wide reset is forced. Needless to + * say, only really *critical* code should be marked as a critical section! + * Currently, this mechanism is only used for XLOG-related code. * *****************************************************************************/ -/* in globals.c */ -/* these are marked volatile because they are set by signal handlers: */ -extern PGDLLIMPORT volatile sig_atomic_t InterruptPending; -extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending; -extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending; -extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending; -extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending; -extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending; -extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending; -extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending; -extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending; - -extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending; -extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost; - -/* these are marked volatile because they are examined by signal handlers: */ -extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount; -extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount; -extern PGDLLIMPORT volatile uint32 CritSectionCount; - -/* in tcop/postgres.c */ -extern void ProcessInterrupts(void); - -/* Test whether an interrupt is pending */ -#ifndef WIN32 -#define INTERRUPTS_PENDING_CONDITION() \ - (unlikely(InterruptPending)) -#else -#define INTERRUPTS_PENDING_CONDITION() \ - (unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? pgwin32_dispatch_queued_signals() : 0, \ - unlikely(InterruptPending)) -#endif - -/* Service interrupt, if one is pending and it's safe to service it now */ -#define CHECK_FOR_INTERRUPTS() \ -do { \ - if (INTERRUPTS_PENDING_CONDITION()) \ - ProcessInterrupts(); \ -} while(0) - -/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */ -#define INTERRUPTS_CAN_BE_PROCESSED() \ - (InterruptHoldoffCount == 0 && CritSectionCount == 0 && \ - QueryCancelHoldoffCount == 0) - -#define HOLD_INTERRUPTS() (InterruptHoldoffCount++) - -#define RESUME_INTERRUPTS() \ -do { \ - Assert(InterruptHoldoffCount > 0); \ - InterruptHoldoffCount--; \ -} while(0) - -#define HOLD_CANCEL_INTERRUPTS() (QueryCancelHoldoffCount++) - -#define RESUME_CANCEL_INTERRUPTS() \ -do { \ - Assert(QueryCancelHoldoffCount > 0); \ - QueryCancelHoldoffCount--; \ -} while(0) - #define START_CRIT_SECTION() (CritSectionCount++) #define END_CRIT_SECTION() \ diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 4aad10b0b6d..be523d1ab3e 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -605,7 +605,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object, extern void pgstat_drop_database(Oid databaseid); extern void pgstat_report_autovac(Oid dboid); -extern void pgstat_report_recovery_conflict(int reason); +extern void pgstat_report_recovery_conflict(InterruptType reason); extern void pgstat_report_deadlock(void); extern void pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount); extern void pgstat_report_checksum_failure(void); diff --git a/src/include/postmaster/interrupt.h b/src/include/postmaster/interrupt.h index 09dc6f0330d..5dcb35cf896 100644 --- a/src/include/postmaster/interrupt.h +++ b/src/include/postmaster/interrupt.h @@ -10,42 +10,110 @@ * * Interrupt flags can be "raised" synchronously by code that wants to defer * an action, or asynchronously by timer signal handlers, other signal - * handlers or "sent" by other backends setting them directly. + * handlers, or "sent" by other backends setting them directly. * - * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is - * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query - * cancellation or idle session timeout. Well behaved backend code performs - * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never - * sleep using mechanisms other than the WaitEventSet mechanism or the more - * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for - * bounded short periods, eg LWLock waits), so they should react in good time. + * Standard interrupts + * ------------------- * - * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and - * consists of tasks that are safe to perform at most times. They can be - * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS(). + * Some interrupts need to be processed fairly quickly even when the backend + * is busy, like QueryCancel (SIGINT) and ProcDie (SIGTERM), but that requires + * cleaning up the current transaction gracefully, and there's no guarantee + * that internal data structures would be self-consistent if the code is + * interrupted at an arbitrary instant. * + * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots + * where it is normally safe to accept a cancel or die interrupt. + * Well-behaved backend code performs CHECK_FOR_INTERRUPTS() periodically in + * long computations, and should never sleep using mechanisms other than the + * WaitEventSet mechanism or the more convenient WaitInterrupt / + * WaitSockerOrInterrupt functions (except for bounded short periods, eg + * LWLock waits), so they should react in good time. * - * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is: + * When you need to wait, pass INTERRUPT_CFI_MASK() to WaitInterrupt() and + * call CHECK_FOR_INTERRUPTS() every time WaitInterrupt() returns. + * + * In some cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level + * subroutines that might sometimes be called in contexts that do *not* want + * to allow a cancel or die interrupt. The HOLD_INTERRUPTS() and + * RESUME_INTERRUPTS() macros allow code to ensure that no cancel or die + * interrupt will be accepted, even if CHECK_FOR_INTERRUPTS() gets called in a + * subroutine. The interrupt will be held off until CHECK_FOR_INTERRUPTS() is + * done outside any HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section. There + * is also a mechanism to prevent query cancel interrupts, while still + * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and + * RESUME_CANCEL_INTERRUPTS(). + * + * Note that ProcessInterrupts() has also acquired a number of tasks that do + * not necessarily cause a query-cancel-or-die response. Hence, it's possible + * that it will just clear some interrupt bits and return. + * + * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an + * interrupt needs to be serviced, without trying to do so immediately. + * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(), + * which tells whether ProcessInterrupts is sure to clear the interrupt. + * + * Special mechanisms are used to let an interrupt be accepted when we are + * waiting for a lock or when we are waiting for command input (but, of + * course, only if the interrupt holdoff counter is zero). See the related + * code for details. + * + * A lost connection is handled similarly to a ProcDie request, although the + * loss of connection is detected and the interrupt is raied when we fail to + * write to the socket. If there was a signal for a broken connection, we + * could make use of it by setting ClientConnectionLost in the signal handler. + * + * Standard interrupts in AUX processes + * ------------------------------------ + * + * In background processes that are not regular backends, responses to signals + * that are translated to interrupts are fairly varied and many types of + * backends have their own implementations. Some use CHECK_FOR_INTERRUPTS() + * but have additional interrupts that are also processed in the main + * loop. Others don't use CHECK_FOR_INTERRUPTS() at all, but have their own + * Process*Interrupts() function that is called at strategic spots. + * + * We nevertheless provide a few generic signal handlers and interrupt checks + * to facilitate code reuse, see ProcessMainLoopInterrupts() and the standard + * signal handlers SignalHandlerForConfigReload(), SignalHandlerForCrashExit(), + * and SignalHandlerForShutdownRequest(). + * + * INTERRUPT_GENERAL: The multiplexed general-purpose wakeup + * --------------------------------------------------------- + * + * The INTERRUPT_GENERAL interrupt is multiplexed for many different purposes + * that don't warrant a dedicated interrupt bit. Because it's reused for + * different purposes, waiters must tolerate receiving spurious interrupt + * wakeups. + * + * Waiting on an interrupt + * ----------------------- + * + * The correct pattern to wait for event(s) using INTERRUPT_GENERAL (or any + * bespoken interrupt flag) is: * * for (;;) * { + * CHECK_FOR_INTERRUPTS(); + * * ClearInterrupt(INTERRUPT_GENERAL); * if (work to do) * Do Stuff(); - * WaitInterrupt(INTERRUPT_GENERAL, ...); + * WaitInterrupt(INTERRUPT_CFI_MASAK() | INTERRUPT_GENERAL, ...); * } * * It's important to clear the interrupt *before* checking if there's work to - * do. Otherwise, if someone sets the interrupt between the check and the + * do. Otherwise, if someone sets the interrupt between the check and the * ClearInterrupt() call, you will miss it and Wait will incorrectly block. * * Another valid coding pattern looks like: * * for (;;) * { + * CHECK_FOR_INTERRUPTS(); + * * if (work to do) * Do Stuff(); // in particular, exit loop if some condition satisfied - * WaitInterrupt(INTERRUPT_GENERAL, ...); + * WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, ...); * ClearInterrupt(INTERRUPT_GENERAL); * } * @@ -55,12 +123,23 @@ * be avoided is placing any checks for asynchronous events after * WaitInterrupt and before ClearInterrupt, as that creates a race condition. * - * To wake up the waiter, you must first set a global flag or something else - * that the wait loop tests in the "if (work to do)" part, and call - * SendInterrupt(INTERRUPT_GENERAL) *after* that. SendInterrupt is designed to - * return quickly if the interrupt is already set. In more complex scenarios - * with nested loops that can consume different events, you can define your - * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL. + * To wake up a process waiting on INTERRUPT_GENERAL, you must first set a + * global flag or something else that the wait loop tests in the "if (work to + * do)" part, and call SendInterrupt(INTERRUPT_GENERAL) *after* + * that. SendInterrupt is designed to return quickly if the interrupt is + * already set. + * + * In more complex scenarios with nested loops that can consume different + * events, you can define your own INTERRUPT_* flag instead of relying on + * INTERRUPT_GENERAL. + * + * Standard Signal handlers + * ------------------------ + * + * Responses to signals that are translated to interrupts are fairly varied + * and many types of backends have their own implementations, but we provide a + * few generic signal handlers and interrupt checks to facilitate code reuse. + * * * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -76,73 +155,272 @@ #include "port/atomics.h" #include "storage/procnumber.h" -#include "storage/waiteventset.h" - -#include +#include "storage/waiteventset.h" /* WL_* are defined in waiteventset.h */ extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts; -extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending; -extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending; +/* these are marked volatile because they are examined by signal handlers: */ +/* FIXME: is that still true, do these still need to be volatile? */ +extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount; +extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount; +extern PGDLLIMPORT volatile uint32 CritSectionCount; + +/* + * If you called ProcessInterrupts() now, it would process and clear these + * interrupts. + */ +#define INTERRUPT_CFI_MASK() \ + ( \ + (InterruptHoldoffCount > 0 || CritSectionCount > 0) ? 0 : \ + ((QueryCancelHoldoffCount > 0) ? (INTERRUPT_CFI_ALL_MASK & (~INTERRUPT_QUERY_CANCEL)) : \ + INTERRUPT_CFI_ALL_MASK) \ + ) + +/* + * Service standard interrupts, if one is pending and it's safe to service it now. + * + * This could check INTERRUPT_CFI_MASK(), but we prefer to keep his short and + * fast in the common case that no interrupts are pending. That's why this + * checks the constant INTERRUPT_CFI_ALL_MASK instead and check the holdoffs + * are out-of-line in ProcessInterrupts(). + */ +#define CHECK_FOR_INTERRUPTS() \ +do { \ + if (IsInterruptPending(INTERRUPT_CFI_ALL_MASK)) \ + ProcessInterrupts(); \ +} while(0) + +/* Would ProcessInterrupts() clear all the bits in 'mask'? */ +#define INTERRUPTS_CAN_BE_PROCESSED(mask) \ + (((mask) & ~INTERRUPT_CFI_MASK()) == 0) + +#define HOLD_INTERRUPTS() \ +do { \ + InterruptHoldoffCount++; \ +} while(0) + +#define RESUME_INTERRUPTS() \ +do { \ + Assert(InterruptHoldoffCount > 0); \ + InterruptHoldoffCount--; \ +} while(0) + +#define HOLD_CANCEL_INTERRUPTS() (QueryCancelHoldoffCount++) + +#define RESUME_CANCEL_INTERRUPTS() \ +do { \ + Assert(QueryCancelHoldoffCount > 0); \ + QueryCancelHoldoffCount--; \ +} while(0) + /* * Flags in the pending interrupts bitmask. Each value represents one bit in * the bitmask. */ -typedef enum +typedef enum InterruptType { /* - * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked - * waiting for an interrupt. If it's set, the backend needs to be woken up - * when a bit in the pending interrupts mask is set. It's used internally - * by the interrupt machinery, and cannot be used directly in the public - * functions. It's named differently to distinguish it from the actual - * interrupt flags. + * INTERRUPT_GENERAL is used as a general-purpose wakeup, multiplexed for + * many reasons. */ - SLEEPING_ON_INTERRUPTS = 1 << 0, + INTERRUPT_GENERAL = 1 << 0, /* - * INTERRUPT_GENERAL is multiplexed for many reasons, like query - * cancellation termination requests, recovery conflicts, and config - * reload requests. Upon receiving INTERRUPT_GENERAL, you should call - * CHECK_FOR_INTERRUPTS() to process those requests. It is also used for - * various other context-dependent purposes, but note that if it's used to - * wake up for other reasons, you must still call CHECK_FOR_INTERRUPTS() - * once per iteration. + * 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 + * INTERRUPT_SINVAL_CATCHUP to any backend that gets too far behind. + * + * The interrupt is processed whenever starting to read from the client, + * or when interrupted while doing so, ProcessClientReadInterrupt() will + * call ProcessCatchupInterrupt(). */ - INTERRUPT_GENERAL = 1 << 1, + INTERRUPT_SINVAL_CATCHUP = 1 << 1, /* - * INTERRUPT_RECOVERY_CONTINUE is used to wake up startup process, to tell - * it that it should continue WAL replay. It's sent by WAL receiver when - * more WAL arrives, or when promotion is requested. + * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered + * to LISTEN on any channels that they might have messages they need to + * deliver to the frontent. It is also processed whenever starting to read + * from the client or while doing so, but only when there is no + * transaction in progress. */ - INTERRUPT_RECOVERY_CONTINUE = 1 << 2, + INTERRUPT_ASYNC_NOTIFY = 1 << 2, + + /* Raised by timer while idle, to send a stats update */ + INTERRUPT_IDLE_STATS_TIMEOUT = 1 << 3, + + /* Config file reload is requested */ + INTERRUPT_CONFIG_RELOAD = 1 << 4, + + /* + * INTERRUPT_RECOVERY_CONTINUE is used to wake up the startup process, to + * tell it that it should continue WAL replay. It's sent by WAL receiver + * when more WAL arrives, or when promotion is requested. We don't reuse + * INTERRUPT_GENERAL for this, so that more WAL arriving doesn't wake up + * the startup process excessively when we're waiting in other places, + * like for recovery conflicts. + */ + INTERRUPT_RECOVERY_CONTINUE = 1 << 5, /* sent to logical replication launcher, when a subscription changes */ - INTERRUPT_SUBSCRIPTION_CHANGE = 1 << 3, + INTERRUPT_SUBSCRIPTION_CHANGE = 1 << 6, + + /* + * Many aux processes don't want to react to INTERRUPT_DIE in + * CHECK_FOR_INTERRUPTS(), so they use a separate flag when shutdown is + * requested. + * + * TODO: perhaps use INTERRUPT_DIE, but teach CHECK_FOR_INTERRUPTS() to + * ignore it in aux processes, and remove it from CheckForInterruptsMask. + * That would save one interrupt bit, and would make things more + * consistent. + */ + INTERRUPT_SHUTDOWN_AUX = 1 << 7, + + /* + * Perform one last checkpoint, then shut down. Only used in the checkpointer + * process. + */ + INTERRUPT_SHUTDOWN_XLOG = 1 << 8, + + /*---- Interrupts handled by CHECK_FOR_INTERRUPTS() ----*/ + + /* + * Backend has been requested to terminate + * + * This is raised by the SIGTERM signal handler, or can be sent directly + * by another backend e.g. with pg_terminate_backend(). + */ + INTERRUPT_DIE = 1 << 9, + + /* + * Cancel current query, if any. + * + * This is raised by the SIGTERM signal handler, or can be sent directly + * by another backend e.g. with pg_cancel_backend(), or in response to a + * query cancellation packet. + */ + INTERRUPT_QUERY_CANCEL = 1 << 10, + + /* ask walsenders to prepare for shutdown */ + INTERRUPT_WALSND_INIT_STOPPING = 1 << 11, + + /* + * Recovery conflict reasons. These are sent by the startup process in hot + * standby mode when a backend holds back the WAL replay for too long. + */ + INTERRUPT_RECOVERY_CONFLICT_DATABASE = 1 << 12, + INTERRUPT_RECOVERY_CONFLICT_TABLESPACE = 1 << 13, + INTERRUPT_RECOVERY_CONFLICT_LOCK = 1 << 14, + INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT = 1 << 15, + INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN = 1 << 16, + INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK = 1 << 17, + INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT = 1 << 18, + + /* Raised by timers */ + INTERRUPT_TRANSACTION_TIMEOUT = 1 << 19, + INTERRUPT_IDLE_SESSION_TIMEOUT = 1 << 20, + INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT = 1 << 21, + INTERRUPT_CLIENT_CHECK_TIMEOUT = 1 << 22, + + /* Raised synchronously when the client connection is lost */ + INTERRUPT_CLIENT_CONNECTION_LOST = 1 << 23, + + /* Ask backend to log the memory contexts */ + INTERRUPT_LOG_MEMORY_CONTEXT = 1 << 24, + + /* Message from a cooperating parallel backend */ + INTERRUPT_PARALLEL_MESSAGE = 1 << 25, + + /* Message from a parallel apply worker */ + INTERRUPT_PARALLEL_APPLY_MESSAGE = 1 << 26, + + /* procsignal global barrier interrupt */ + INTERRUPT_BARRIER = 1 << 27, + + /*---- end of interrupts handled by CHECK_FOR_INTERRUPTS() ----*/ + + /* + * NOTE: InterruptTypes must fit in a 32-bit bitmask. (If we had efficient + * 64-bit atomics on all platforms, we could easily go up to 64 bits) + */ + + /* + * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked + * waiting for an interrupt. If it's set, the backend needs to be woken up + * when a bit in the pending interrupts mask is set. It's used internally + * by the interrupt machinery, and cannot be used directly in the public + * functions. It's named differently to distinguish it from the actual + * interrupt flags. + */ + SLEEPING_ON_INTERRUPTS = 1 << 31, + } InterruptType; +/* The set of interrupts that are (ever) processed by CHECK_FOR_INTERRUPTS */ +#define INTERRUPT_CFI_ALL_MASK ( \ + INTERRUPT_DIE | \ + INTERRUPT_QUERY_CANCEL | \ + INTERRUPT_WALSND_INIT_STOPPING | \ + INTERRUPT_RECOVERY_CONFLICT_DATABASE | \ + INTERRUPT_RECOVERY_CONFLICT_TABLESPACE | \ + INTERRUPT_RECOVERY_CONFLICT_LOCK | \ + INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT | \ + INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN | \ + INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK | \ + INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT | \ + INTERRUPT_TRANSACTION_TIMEOUT | \ + INTERRUPT_IDLE_SESSION_TIMEOUT | \ + INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT | \ + INTERRUPT_CLIENT_CHECK_TIMEOUT | \ + INTERRUPT_CLIENT_CONNECTION_LOST | \ + INTERRUPT_LOG_MEMORY_CONTEXT | \ + INTERRUPT_PARALLEL_MESSAGE | \ + INTERRUPT_PARALLEL_APPLY_MESSAGE | \ + INTERRUPT_BARRIER \ + ) + +/* The set of interrupts that are processed by ProcessMainLoopInterrupts */ +#define INTERRUPT_MAIN_LOOP_MASK ( \ + INTERRUPT_BARRIER | \ + INTERRUPT_SHUTDOWN_AUX | \ + INTERRUPT_LOG_MEMORY_CONTEXT | \ + INTERRUPT_CONFIG_RELOAD \ + ) + /* - * Test an interrupt flag (of flags). + * Test an interrupt flag (or flags). */ static inline bool IsInterruptPending(uint32 interruptMask) { - return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0; + pg_read_barrier(); + +#ifdef WIN32 + if (unlikely(UNBLOCKED_SIGNAL_QUEUE())) + pgwin32_dispatch_queued_signals(); +#endif + + if (unlikely((pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0)) + return true; + else + return false; } /* - * Clear an interrupt flag. + * Clear an interrupt flag (or flags). */ static inline void ClearInterrupt(uint32 interruptMask) { pg_atomic_fetch_and_u32(MyPendingInterrupts, ~interruptMask); + pg_write_barrier(); } /* - * Test and clear an interrupt flag. + * Test and clear an interrupt flag (or flags). */ static inline bool ConsumeInterrupt(uint32 interruptMask) @@ -155,9 +433,6 @@ ConsumeInterrupt(uint32 interruptMask) return true; } -extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending; -extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending; - extern void RaiseInterrupt(uint32 interruptMask); extern void SendInterrupt(uint32 interruptMask, ProcNumber pgprocno); extern int WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout, @@ -173,4 +448,7 @@ extern void SignalHandlerForConfigReload(SIGNAL_ARGS); extern void SignalHandlerForCrashExit(SIGNAL_ARGS); extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS); +/* in tcop/postgres.c */ +extern void ProcessInterrupts(void); + #endif diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h index 158f52255a6..a0316202b95 100644 --- a/src/include/postmaster/startup.h +++ b/src/include/postmaster/startup.h @@ -25,6 +25,14 @@ extern PGDLLIMPORT int log_startup_progress_interval; +/* The set of interrupts that are processed by ProcessStartupProcInterrupts */ +#define INTERRUPT_STARTUP_PROC_MASK ( \ + INTERRUPT_BARRIER | \ + INTERRUPT_DIE | \ + INTERRUPT_LOG_MEMORY_CONTEXT | \ + INTERRUPT_CONFIG_RELOAD \ + ) + extern void ProcessStartupProcInterrupts(void); extern void StartupProcessMain(const void *startup_data, size_t startup_data_len) pg_attribute_noreturn(); extern void PreRestoreCommand(void); diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h index 88912606e4d..8900d1ed92f 100644 --- a/src/include/replication/logicalworker.h +++ b/src/include/replication/logicalworker.h @@ -12,10 +12,6 @@ #ifndef LOGICALWORKER_H #define LOGICALWORKER_H -#include - -extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending; - extern void ApplyWorkerMain(Datum main_arg); extern void ParallelApplyWorkerMain(Datum main_arg); extern void TablesyncWorkerMain(Datum main_arg); @@ -23,7 +19,6 @@ extern void TablesyncWorkerMain(Datum main_arg); extern bool IsLogicalWorker(void); extern bool IsLogicalParallelApplyWorker(void); -extern void HandleParallelApplyMessageInterrupt(void); extern void ProcessParallelApplyMessages(void); extern void LogicalRepWorkersWakeupAtCommit(Oid subid); diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h index f5a24ccfbf2..b0ac9f70ae2 100644 --- a/src/include/replication/slot.h +++ b/src/include/replication/slot.h @@ -160,8 +160,8 @@ typedef struct ReplicationSlot /* is this slot defined */ bool in_use; - /* Who is streaming out changes for this slot? 0 in unused slots. */ - pid_t active_pid; + /* Who is streaming out changes for this slot? INVALID_PROC_NUMBER in unused slots. */ + ProcNumber active_proc; /* any outstanding modifications? */ bool just_dirtied; @@ -187,7 +187,7 @@ typedef struct ReplicationSlot /* is somebody performing io on this slot? */ LWLock io_in_progress_lock; - /* Condition variable signaled when active_pid changes */ + /* Condition variable signaled when active_proc changes */ ConditionVariable active_cv; /* all the remaining data is only used for logical slots */ diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h index c3e8e191339..24026fcfe5c 100644 --- a/src/include/replication/walsender.h +++ b/src/include/replication/walsender.h @@ -46,7 +46,7 @@ extern void WalSndShmemInit(void); extern void WalSndWakeup(bool physical, bool logical); extern void WalSndInitStopping(void); extern void WalSndWaitStopping(void); -extern void HandleWalSndInitStopping(void); +extern void ProcessWalSndInitStopping(void); extern void WalSndRqstFileReload(void); /* diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h index 814b812432a..3f07fed7a35 100644 --- a/src/include/replication/walsender_private.h +++ b/src/include/replication/walsender_private.h @@ -40,6 +40,7 @@ typedef enum WalSndState */ typedef struct WalSnd { + ProcNumber pgprocno; /* this walsender's ProcNumber, or invalid if not active */ pid_t pid; /* this walsender's PID, or 0 if not active */ WalSndState state; /* this walsender's state */ diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h index 30b2775952c..6781e9f6a6a 100644 --- a/src/include/replication/worker_internal.h +++ b/src/include/replication/worker_internal.h @@ -78,9 +78,10 @@ typedef struct LogicalRepWorker FileSet *stream_fileset; /* - * PID of leader apply worker if this slot is used for a parallel apply - * worker, InvalidPid otherwise. + * Leader apply worker if this slot is used for a parallel apply worker, + * INVALID_PROC_NUMBER/InvalidPid otherwise. */ + ProcNumber leader_pgprocno; pid_t leader_pid; /* Indicates whether apply can be performed in parallel. */ diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index ef0b733ebe8..59a264059bc 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -14,6 +14,7 @@ #ifndef PROCARRAY_H #define PROCARRAY_H +#include "postmaster/interrupt.h" #include "storage/lock.h" #include "storage/standby.h" #include "utils/relcache.h" @@ -77,14 +78,14 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin, bool excludeXmin0, bool allDbs, int excludeVacuum, int *nvxids); extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid); -extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode); -extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode, +extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, InterruptType reason); +extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason, bool conflictPending); extern bool MinimumActiveBackends(int min); extern int CountDBBackends(Oid databaseid); extern int CountDBConnections(Oid databaseid); -extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending); +extern void CancelDBBackends(Oid databaseid, InterruptType reason, bool conflictPending); extern int CountUserBackends(Oid roleid); extern bool CountOtherDBBackends(Oid databaseId, int *nbackends, int *nprepared); diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h index 022fd8ed933..8d917f1bec4 100644 --- a/src/include/storage/procsignal.h +++ b/src/include/storage/procsignal.h @@ -14,43 +14,6 @@ #ifndef PROCSIGNAL_H #define PROCSIGNAL_H -#include "storage/procnumber.h" - - -/* - * Reasons for signaling a Postgres child process (a backend or an auxiliary - * process, like checkpointer). We can cope with concurrent signals for different - * reasons. However, if the same reason is signaled multiple times in quick - * succession, the process is likely to observe only one notification of it. - * This is okay for the present uses. - * - * Also, because of race conditions, it's important that all the signals be - * defined so that no harm is done if a process mistakenly receives one. - */ -typedef enum -{ - PROCSIG_CATCHUP_INTERRUPT, /* sinval catchup interrupt */ - PROCSIG_NOTIFY_INTERRUPT, /* listen/notify interrupt */ - PROCSIG_PARALLEL_MESSAGE, /* message from cooperating parallel backend */ - PROCSIG_WALSND_INIT_STOPPING, /* ask walsenders to prepare for shutdown */ - PROCSIG_BARRIER, /* global barrier interrupt */ - PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */ - PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */ - - /* Recovery conflict reasons */ - PROCSIG_RECOVERY_CONFLICT_FIRST, - PROCSIG_RECOVERY_CONFLICT_DATABASE = PROCSIG_RECOVERY_CONFLICT_FIRST, - PROCSIG_RECOVERY_CONFLICT_TABLESPACE, - PROCSIG_RECOVERY_CONFLICT_LOCK, - PROCSIG_RECOVERY_CONFLICT_SNAPSHOT, - PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT, - PROCSIG_RECOVERY_CONFLICT_BUFFERPIN, - PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK, - PROCSIG_RECOVERY_CONFLICT_LAST = PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK, -} ProcSignalReason; - -#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT_LAST + 1) - typedef enum { PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */ @@ -63,16 +26,12 @@ extern Size ProcSignalShmemSize(void); extern void ProcSignalShmemInit(void); extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key); -extern int SendProcSignal(pid_t pid, ProcSignalReason reason, - ProcNumber procNumber); extern void SendCancelRequest(int backendPID, int32 cancelAuthCode); extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type); extern void WaitForProcSignalBarrier(uint64 generation); extern void ProcessProcSignalBarrier(void); -extern void procsignal_sigusr1_handler(SIGNAL_ARGS); - /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */ typedef struct ProcSignalHeader ProcSignalHeader; diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h index 2463c0f9fac..cc627ae4853 100644 --- a/src/include/storage/sinval.h +++ b/src/include/storage/sinval.h @@ -125,16 +125,11 @@ typedef union /* Counter of messages processed; don't worry about overflow. */ extern PGDLLIMPORT uint64 SharedInvalidMessageCounter; -extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending; - extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs, int n); extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg), void (*resetFunction) (void)); -/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */ -extern void HandleCatchupInterrupt(void); - /* * enable/disable processing of catchup events directly from signal handler. * The enable routine first performs processing of any catchup events that diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h index 24e2f5082bc..881506d609f 100644 --- a/src/include/storage/standby.h +++ b/src/include/storage/standby.h @@ -15,8 +15,8 @@ #define STANDBY_H #include "datatype/timestamp.h" +#include "postmaster/interrupt.h" #include "storage/lock.h" -#include "storage/procsignal.h" #include "storage/relfilelocator.h" #include "storage/standbydefs.h" @@ -43,7 +43,7 @@ extern void CheckRecoveryConflictDeadlock(void); extern void StandbyDeadLockHandler(void); extern void StandbyTimeoutHandler(void); extern void StandbyLockTimeoutHandler(void); -extern void LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start, +extern void LogRecoveryConflict(InterruptType reason, TimestampTz wait_start, TimestampTz now, VirtualTransactionId *wait_list, bool still_waiting); diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h index 60ececb785c..c9ab8136b53 100644 --- a/src/include/storage/waiteventset.h +++ b/src/include/storage/waiteventset.h @@ -27,7 +27,6 @@ #include #include "storage/procnumber.h" -#include "utils/resowner.h" /* * Bitmasks for events that may wake-up WaitInterrupt(), @@ -82,7 +81,9 @@ extern void InitializeWaitEventSupport(void); extern HANDLE CreateSharedWakeupHandle(void); #endif -extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents); +struct ResourceOwnerData; + +extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents); extern void FreeWaitEventSet(WaitEventSet *set); extern void FreeWaitEventSetAfterFork(WaitEventSet *set); extern int AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h index a62367f7793..908f39217b0 100644 --- a/src/include/tcop/tcopprot.h +++ b/src/include/tcop/tcopprot.h @@ -16,7 +16,6 @@ #include "nodes/params.h" #include "nodes/plannodes.h" -#include "storage/procsignal.h" #include "utils/guc.h" #include "utils/queryenvironment.h" @@ -72,9 +71,8 @@ extern void die(SIGNAL_ARGS); extern void quickdie(SIGNAL_ARGS) pg_attribute_noreturn(); extern void StatementCancelHandler(SIGNAL_ARGS); extern void FloatExceptionHandler(SIGNAL_ARGS) pg_attribute_noreturn(); -extern void HandleRecoveryConflictInterrupt(ProcSignalReason reason); -extern void ProcessClientReadInterrupt(bool blocked); -extern void ProcessClientWriteInterrupt(bool blocked); +extern uint32 ProcessClientReadInterrupt(bool blocked); +extern uint32 ProcessClientWriteInterrupt(bool blocked); extern void process_postgres_switches(int argc, char *argv[], GucContext ctx, const char **dbname); diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h index 8abc26abce2..5dce202fe90 100644 --- a/src/include/utils/memutils.h +++ b/src/include/utils/memutils.h @@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context); #define MemoryContextCopyAndSetIdentifier(cxt, id) \ MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id)) -extern void HandleLogMemoryContextInterrupt(void); extern void ProcessLogMemoryContextInterrupt(void); /* diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c index 02aa420360c..ebf0bffb624 100644 --- a/src/test/modules/test_shm_mq/setup.c +++ b/src/test/modules/test_shm_mq/setup.c @@ -263,6 +263,9 @@ wait_for_workers_to_become_ready(worker_state *wstate, { int workers_ready; + /* Clear the interrupt flag so we don't spin. */ + ClearInterrupt(INTERRUPT_GENERAL); + /* If all the workers are ready, we have succeeded. */ SpinLockAcquire(&hdr->mutex); workers_ready = hdr->workers_ready; @@ -285,12 +288,10 @@ wait_for_workers_to_become_ready(worker_state *wstate, we_bgworker_startup = WaitEventExtensionNew("TestShmMqBgWorkerStartup"); /* Wait to be signaled. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, we_bgworker_startup); - /* Clear the interrupt flag so we don't spin. */ - ClearInterrupt(INTERRUPT_GENERAL); - /* An interrupt may have occurred while we were waiting. */ CHECK_FOR_INTERRUPTS(); } diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c index 240fe315d47..101f6b7741c 100644 --- a/src/test/modules/test_shm_mq/test.c +++ b/src/test/modules/test_shm_mq/test.c @@ -171,6 +171,8 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS) { bool wait = true; + ClearInterrupt(INTERRUPT_GENERAL); + /* * If we haven't yet sent the message the requisite number of times, * try again to send it now. Note that when shm_mq_send() returns @@ -239,9 +241,9 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS) * they have read or written data and therefore there may now be * work for us to do. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | INTERRUPT_GENERAL, + WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0, we_message_queue); - ClearInterrupt(INTERRUPT_GENERAL); CHECK_FOR_INTERRUPTS(); } } diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c index 28a2798037a..6dcd4141c0b 100644 --- a/src/test/modules/worker_spi/worker_spi.c +++ b/src/test/modules/worker_spi/worker_spi.c @@ -216,7 +216,9 @@ worker_spi_main(Datum main_arg) * is awakened if postmaster dies. That way the background process * goes away immediately in an emergency. */ - (void) WaitInterrupt(INTERRUPT_GENERAL, + (void) WaitInterrupt(INTERRUPT_CFI_MASK() | + INTERRUPT_CONFIG_RELOAD | + INTERRUPT_GENERAL, WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, worker_spi_naptime * 1000L, worker_spi_wait_event_main); @@ -227,11 +229,8 @@ worker_spi_main(Datum main_arg) /* * In case of a SIGHUP, just reload the configuration. */ - if (ConfigReloadPending) - { - ConfigReloadPending = false; + if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD)) ProcessConfigFile(PGC_SIGHUP); - } /* * Start a transaction on which we can run queries. Note that each -- 2.39.5