From 6bdfa5b40539a10d1a43b2d4c68c79e8bf7879bd Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Tue, 10 Mar 2026 13:14:37 -0400
Subject: [PATCH v12a 3/4] Replace Latches with Interrupts

The Latch was a flag, with an inter-process signalling mechanism that
allowed a process to wait for the Latch to be set. My original vision
for Latches was that you could have different latches for different
things that you need to wait for, but in practice, almost all code
used one "process latch" that was shared for all wakeups. The only
exception was the "recoveryWakeupLatch". I think the reason that we
ended up just sharing the same latch for all wakeups is that it was
cumbersome in practice to deal with multiple latches. For starters,
there was no machinery for waiting for multiple latches at the same
time. Secondly, changing the "ownership" of a latch needed extra
locking or other coordination. Thirdly, each inter-process latch
needed to be initialized at postmaster startup time.

This patch embraces the reality of how Latches were used, and replaces
the Latches with a per-process interrupt mask. You can no longer
allocate Latches in arbitrary shared memory structs and an interrupt
is always directed at a particular process, addressed by its
ProcNumber. Each process has a bitmask of pending interrupts in
PGPROC.

This replaces ProcSignals and many direct uses of Unix signals with
interrupts. For example, the backend-private ProcDiePending flag is
replaced with the INTERRUPT_TERMINATE interrupt. SIGTERM can still be
used to raise it, but the default SIGTERM signal handler now just
raises INTERRUPT_TERMINATE.

Handling interrupts
-------------------

To control which interrupts can be processed at what time, there are a
few mechanisms. Firstly, you can register a handler function for each
interrupt, which will be called from CHECK_FOR_INTERRUPTS() if the
interrupt is pending. After this commit, ProcessInterrupts() has no
knowledge about the meaning of the different interrupts, it just calls
the registered callback functions. Different procesess can set up
different handlers, according to their needs, but some commonly used
handlers are provided and installed during backend startup.

In addition to CHECK_FOR_INTERRUPTS(), you can also check whether a
particular interrupt is pending and handle it yourself.

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() 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_WAIT_WAKEUP 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.

Replacing recoveryWakeupLatch
-----------------------------

The INTERRUPT_RECOVERY_CONTINUE replaces recoveryWakeupLatch. With
this new interface, the startup process can easily wait for the common
interrupts at the same time as INTERRUPT_RECOVERY_CONTINUE.
Previously, the code that waited on recoveryWakeupLatch had to resort
to timeouts to react to other signals, because it was not possible to
wait for two latches at the same time. Previous attempt at unifying
those wakeups was in commit ac22929a26, but it was reverted in commit
00f690a239 because it caused a lot of spurious wakeups when the
startup process was waiting for recovery conflicts. The new machinery
avoids that problem by making it easy to wait for two interrupts at
the same time.

Fix lost wakeup issue in logical replication launcher
-----------------------------------------------------

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/476672e7-62f1-4cab-a822-f3a8e949dd3f@iki.fi
  Discussion: https://www.postgresql.org/message-id/ff0663d9-8011-420f-a169-efbf57327cb5@iki.fi

Background worker state change notification
-------------------------------------------

INTERRUPT_WAIT_WAKEUP is used for bgworker state change notification.
As part of a project to remove the reliance on PIDs to identify
backends, backends that register dynamic background workers will now
receive INTERRUPT_WAIT_WAKEUP sent directly by the postmaster when the
worker state changes.  Previously, the postmaster would send a SIGUSR1
signal, which relied on the ProcSignal system's handler setting the
interrupt flag, with the same end effect.  Now that intermediate step
is skipped.

The field bgw_notify_pid still exists for backwards compatibility, but
has no effect and may be removed in a later release.
RegisterBackgroundWorker() now automatically assumes that the caller
would like state change notifications delivered to its proc number,
unless BGW_NO_NOTIFY is set in bgw_flags.

Removing other cases of PIDs from the bgworker API is left for later
work; this patch is concerned only with removing a dependency on
ProcSignal infrastructure that is due to be removed by a later commit.

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).
---
 src/include/Makefile                          |   1 +
 src/include/access/parallel.h                 |   2 -
 src/include/access/xlogrecovery.h             |  18 -
 src/include/commands/async.h                  |   6 -
 src/include/ipc/interrupt.h                   | 370 +++++++++++
 .../interrupt.h => ipc/signal_handlers.h}     |   6 +-
 src/include/libpq/libpq-be-fe-helpers.h       |  48 +-
 src/include/libpq/libpq.h                     |   2 +-
 src/include/libpq/pqmq.h                      |   2 +-
 src/include/meson.build                       |   1 +
 src/include/miscadmin.h                       | 135 +---
 src/include/postmaster/bgworker.h             |  10 +-
 src/include/postmaster/bgworker_internals.h   |   4 +-
 src/include/postmaster/startup.h              |   4 +-
 src/include/replication/logicalworker.h       |   5 -
 src/include/replication/walsender.h           |   1 -
 src/include/replication/walsender_private.h   |   2 +
 src/include/replication/worker_internal.h     |   5 +-
 src/include/storage/proc.h                    |  16 +-
 src/include/storage/procarray.h               |   2 +-
 src/include/storage/procsignal.h              |  33 -
 src/include/storage/sinval.h                  |   5 -
 src/include/storage/waiteventset.h            |  39 +-
 src/include/tcop/tcopprot.h                   |   7 -
 src/include/utils/memutils.h                  |   1 -
 src/include/utils/pgstat_internal.h           |   1 +
 src/common/scram-common.c                     |   2 +-
 src/port/pg_numa.c                            |   5 +-
 src/port/pgsleep.c                            |   8 +-
 src/backend/Makefile                          |   1 +
 src/backend/access/brin/brin_pageops.c        |   2 +-
 src/backend/access/brin/brin_revmap.c         |   2 +-
 src/backend/access/common/toast_internals.c   |   2 +-
 src/backend/access/gin/ginbtree.c             |   2 +-
 src/backend/access/gin/gindatapage.c          |   2 +-
 src/backend/access/gin/ginfast.c              |   1 +
 src/backend/access/gin/ginget.c               |   1 +
 src/backend/access/gin/ginutil.c              |   2 +-
 src/backend/access/gin/ginvacuum.c            |   1 +
 src/backend/access/gist/gist.c                |   1 +
 src/backend/access/gist/gistget.c             |   2 +-
 src/backend/access/gist/gistvacuum.c          |   2 +-
 src/backend/access/hash/hash.c                |   2 +-
 src/backend/access/hash/hashinsert.c          |   2 +-
 src/backend/access/hash/hashovfl.c            |   2 +-
 src/backend/access/hash/hashpage.c            |   2 +-
 src/backend/access/hash/hashsearch.c          |   2 +-
 src/backend/access/hash/hashsort.c            |   2 +-
 src/backend/access/heap/pruneheap.c           |   2 +-
 src/backend/access/heap/vacuumlazy.c          |  11 +-
 src/backend/access/heap/visibilitymap.c       |   2 +-
 src/backend/access/index/genam.c              |   1 +
 src/backend/access/nbtree/nbtdedup.c          |   2 +-
 src/backend/access/nbtree/nbtinsert.c         |   2 +-
 src/backend/access/nbtree/nbtpage.c           |   1 +
 src/backend/access/nbtree/nbtsearch.c         |   2 +-
 src/backend/access/spgist/spgdoinsert.c       |   9 +-
 src/backend/access/spgist/spginsert.c         |   2 +-
 src/backend/access/spgist/spgscan.c           |   2 +-
 src/backend/access/spgist/spgvacuum.c         |   2 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/README.parallel    |   2 +-
 src/backend/access/transam/generic_xlog.c     |   2 +-
 src/backend/access/transam/parallel.c         |  77 +--
 src/backend/access/transam/slru.c             |   2 +-
 src/backend/access/transam/twophase.c         |   1 +
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/access/transam/xlogfuncs.c        |  12 +-
 src/backend/access/transam/xlogrecovery.c     | 103 ++-
 src/backend/access/transam/xlogwait.c         |  39 +-
 src/backend/backup/basebackup_throttle.c      |  18 +-
 src/backend/bootstrap/bootparse.y             |   2 +-
 src/backend/commands/async.c                  | 152 ++---
 src/backend/commands/copyfromparse.c          |  11 +-
 src/backend/commands/vacuum.c                 |  13 +-
 src/backend/executor/nodeAppend.c             |  23 +-
 src/backend/executor/nodeGather.c             |  11 +-
 src/backend/ipc/Makefile                      |  19 +
 src/backend/ipc/README.md                     | 260 ++++++++
 src/backend/ipc/interrupt.c                   | 431 ++++++++++++
 src/backend/ipc/meson.build                   |   6 +
 src/backend/ipc/signal_handlers.c             | 160 +++++
 src/backend/libpq/auth.c                      |   9 +-
 src/backend/libpq/be-secure-gssapi.c          |  16 +-
 src/backend/libpq/be-secure-openssl.c         |   7 +-
 src/backend/libpq/be-secure.c                 |  62 +-
 src/backend/libpq/pqcomm.c                    |  37 +-
 src/backend/libpq/pqmq.c                      |  30 +-
 src/backend/meson.build                       |   1 +
 src/backend/postmaster/Makefile               |   1 -
 src/backend/postmaster/autovacuum.c           | 151 ++---
 src/backend/postmaster/auxprocess.c           |   2 +-
 src/backend/postmaster/bgworker.c             | 173 ++---
 src/backend/postmaster/bgwriter.c             |  51 +-
 src/backend/postmaster/checkpointer.c         | 191 +++---
 src/backend/postmaster/interrupt.c            | 108 ---
 src/backend/postmaster/meson.build            |   1 -
 src/backend/postmaster/pgarch.c               | 100 ++-
 src/backend/postmaster/postmaster.c           |  57 +-
 src/backend/postmaster/startup.c              | 104 ++-
 src/backend/postmaster/syslogger.c            |  37 +-
 src/backend/postmaster/walsummarizer.c        |  83 ++-
 src/backend/postmaster/walwriter.c            |  46 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   1 -
 .../replication/logical/applyparallelworker.c | 146 ++---
 src/backend/replication/logical/launcher.c    | 123 ++--
 .../replication/logical/sequencesync.c        |   6 +-
 src/backend/replication/logical/slotsync.c    |  90 +--
 src/backend/replication/logical/tablesync.c   |  38 +-
 src/backend/replication/logical/worker.c      |  49 +-
 src/backend/replication/slot.c                |  37 +-
 src/backend/replication/syncrep.c             |  42 +-
 src/backend/replication/walreceiver.c         |  56 +-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           | 228 ++++---
 src/backend/storage/aio/aio.c                 |   2 +-
 src/backend/storage/aio/aio_io.c              |   2 +-
 src/backend/storage/aio/method_worker.c       |  71 +-
 src/backend/storage/buffer/bufmgr.c           |  21 +-
 src/backend/storage/buffer/freelist.c         |  27 +-
 src/backend/storage/ipc/Makefile              |   1 -
 src/backend/storage/ipc/dsm_impl.c            |   4 +-
 src/backend/storage/ipc/ipc.c                 |  12 +-
 src/backend/storage/ipc/latch.c               | 389 -----------
 src/backend/storage/ipc/meson.build           |   1 -
 src/backend/storage/ipc/procarray.c           |  22 +-
 src/backend/storage/ipc/procsignal.c          | 225 +------
 src/backend/storage/ipc/shm_mq.c              | 127 ++--
 src/backend/storage/ipc/signalfuncs.c         |  14 +-
 src/backend/storage/ipc/sinval.c              |  68 +-
 src/backend/storage/ipc/sinvaladt.c           |  22 +-
 src/backend/storage/ipc/standby.c             |  43 +-
 src/backend/storage/ipc/waiteventset.c        | 400 ++++++------
 src/backend/storage/lmgr/condition_variable.c |  35 +-
 src/backend/storage/lmgr/predicate.c          |   9 +-
 src/backend/storage/lmgr/proc.c               | 148 ++---
 src/backend/storage/smgr/smgr.c               |   4 +-
 src/backend/storage/sync/sync.c               |   6 +-
 src/backend/tcop/backend_startup.c            |   8 +
 src/backend/tcop/postgres.c                   | 615 +++++++++---------
 src/backend/utils/adt/jsonpath_gram.y         |   2 +-
 src/backend/utils/adt/mcxtfuncs.c             |  11 +-
 src/backend/utils/adt/misc.c                  |  25 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/error/elog.c                |   1 -
 src/backend/utils/init/globals.c              |  23 -
 src/backend/utils/init/miscinit.c             |  78 +--
 src/backend/utils/init/postinit.c             |  42 +-
 src/backend/utils/misc/timeout.c              |   7 -
 src/backend/utils/mmgr/mcxt.c                 |  24 +-
 contrib/pg_prewarm/autoprewarm.c              |  63 +-
 contrib/postgres_fdw/connection.c             |  21 +-
 contrib/postgres_fdw/postgres_fdw.c           |   4 +-
 doc/src/sgml/bgworker.sgml                    |  32 +-
 doc/src/sgml/oauth-validators.sgml            |   2 +-
 doc/src/sgml/sources.sgml                     |  11 +-
 src/test/isolation/isolationtester.c          |   2 +-
 src/test/modules/test_shm_mq/setup.c          |  15 +-
 src/test/modules/test_shm_mq/test.c           |  16 +-
 src/test/modules/test_shm_mq/test_shm_mq.h    |   2 +
 src/test/modules/test_shm_mq/worker.c         |  12 +-
 src/test/modules/worker_spi/worker_spi.c      |  45 +-
 src/tools/pgindent/typedefs.list              |   3 +-
 163 files changed, 3537 insertions(+), 3442 deletions(-)
 create mode 100644 src/include/ipc/interrupt.h
 rename src/include/{postmaster/interrupt.h => ipc/signal_handlers.h} (82%)
 create mode 100644 src/backend/ipc/Makefile
 create mode 100644 src/backend/ipc/README.md
 create mode 100644 src/backend/ipc/interrupt.c
 create mode 100644 src/backend/ipc/meson.build
 create mode 100644 src/backend/ipc/signal_handlers.c
 delete mode 100644 src/backend/postmaster/interrupt.c
 delete mode 100644 src/backend/storage/ipc/latch.c

diff --git a/src/include/Makefile b/src/include/Makefile
index ac673f4cf17..c474d6fbe3c 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
 	executor \
 	fe_utils \
 	foreign \
+	ipc \
 	jit \
 	lib \
 	libpq \
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index 60f857675e0..6d57c310239 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -55,7 +55,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 +71,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/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2842106b285..7564d23cf7b 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -15,7 +15,6 @@
 #include "catalog/pg_control.h"
 #include "lib/stringinfo.h"
 #include "storage/condition_variable.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 /*
@@ -77,23 +76,6 @@ typedef struct XLogRecoveryCtlData
 	 */
 	bool		SharedPromoteIsTriggered;
 
-	/*
-	 * recoveryWakeupLatch is used to wake up the startup process to continue
-	 * WAL replay, if it is waiting for WAL to arrive or promotion to be
-	 * requested.
-	 *
-	 * Note that the startup process also uses another latch, its procLatch,
-	 * to wait for recovery conflict. If we get rid of recoveryWakeupLatch for
-	 * signaling the startup process in favor of using its procLatch, which
-	 * comports better with possible generic signal handlers using that latch.
-	 * But we should not do that because the startup process doesn't assume
-	 * that it's waken up by walreceiver process or SIGHUP signal handler
-	 * while it's waiting for recovery conflict. The separate latches,
-	 * recoveryWakeupLatch and procLatch, should be used for inter-process
-	 * communication for WAL replay and recovery conflict, respectively.
-	 */
-	Latch		recoveryWakeupLatch;
-
 	/*
 	 * Last record successfully replayed.
 	 */
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 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/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..239d58f7597
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,370 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ */
+#include "storage/waiteventset.h"
+
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * 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 frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * 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.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED 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.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 63
+
+/*
+ * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+ * waiting for an interrupt.  If 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.
+ */
+#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
+
+extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 790724b6a0b..b809073d48c 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -64,7 +64,7 @@ extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
 extern PGDLLIMPORT WaitEventSet *FeBeWaitSet;
 
 #define FeBeWaitSetSocketPos 0
-#define FeBeWaitSetLatchPos 1
+#define FeBeWaitSetInterruptPos 1
 #define FeBeWaitSetNEvents 3
 
 extern int	ListenServerPort(int family, const char *hostName,
diff --git a/src/include/libpq/pqmq.h b/src/include/libpq/pqmq.h
index 36780c0816e..ea96ae9545c 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -18,7 +18,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/meson.build b/src/include/meson.build
index 7d734d92dab..da8129355cc 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -135,6 +135,7 @@ header_subdirs = [
   'executor',
   'fe_utils',
   'foreign',
+  'ipc',
   'jit',
   'lib',
   'libpq',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..476e60a4d06 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,134 +28,17 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/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().
- *
- * 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.
- *
- *****************************************************************************/
-
-/* 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() : (void) 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() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
-
-
 /*****************************************************************************
  *	  globals.h --															 *
  *****************************************************************************/
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-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);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(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/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ 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 WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ 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 4ecbdcfadac..d368bc60536 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,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/proc.h b/src/include/storage/proc.h
index 4a1fafb9038..1ca4c70715c 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -18,7 +18,6 @@
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -251,12 +250,18 @@ typedef struct PGPROC
 	 * Inter-process signaling
 	 ************************************************************************/
 
-	Latch		procLatch;		/* generic latch for process */
-
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 
 	int			delayChkptFlags;	/* for DELAY_CHKPT_* flags */
 
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint64 pendingInterrupts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE          interruptWakeupEvent;
+#endif
+
 	/*
 	 * While in hot standby mode, shows that a conflict signal has been sent
 	 * for the current transaction. Set/cleared while holding ProcArrayLock,
@@ -494,6 +499,8 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 avLauncherProc;
 	pg_atomic_uint32 walwriterProc;
 	pg_atomic_uint32 checkpointerProc;
+	pg_atomic_uint32 walreceiverProc;
+	pg_atomic_uint32 startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -577,9 +584,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +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 */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 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 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,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/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..a7f974c5ba2 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,17 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +73,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+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,
-							  struct Latch *latch, void *user_data);
+							  uint64 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint64 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 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"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void 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 11ab1717a16..5d47643eb77 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/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 4c31e503eb1..0bd50bdb23f 100644
--- a/src/port/pgsleep.c
+++ b/src/port/pgsleep.c
@@ -32,10 +32,10 @@
  *
  * CAUTION: It's not a good idea to use long sleeps in the backend.  They will
  * silently return early if a signal is caught, but that doesn't include
- * latches being set on most OSes, and even signal handlers that set MyLatch
- * might happen to run before the sleep begins, allowing the full delay.
- * Better practice is to use WaitLatch() with a timeout, so that backends
- * respond to latches and signals promptly.
+ * interrupts being received on most OSes, and even signal handlers that raise
+ * an interrupt might happen to run before the sleep begins, allowing the full
+ * delay.  Better practice is to use WaitInterrupt() with a timeout, so that
+ * backends respond to interrupts and signals promptly.
  */
 void
 pg_usleep(long microsec)
diff --git a/src/backend/Makefile b/src/backend/Makefile
index ba53cd9d998..6e1bac8ca7c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..e8e84e248ce 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index c5d7db28077..06c996f6fc0 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb65a..ddf414660e0 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index ff927279cc3..460cd9f9d95 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index c9f143f6c31..b2ac6e247e6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index dfffce3e396..9fc03134804 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index b64ccf5e912..fb7a9ea2a1d 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 65c9f393f41..a8ec4be4b4d 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 82c5b28e0ad..f21e64da834 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,13 +143,13 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
 #include "utils/lsyscache.h"
@@ -3306,11 +3306,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index e21b96281a6..03026efa9ec 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..70a4e4ee00a 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,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.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,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(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
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/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index ab1dfb30e73..52f23f027f0 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -116,9 +117,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;
 
@@ -128,9 +126,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.
@@ -244,7 +239,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -613,7 +608,6 @@ LaunchParallelWorkers(ParallelContext *pcxt)
 	sprintf(worker.bgw_library_name, "postgres");
 	sprintf(worker.bgw_function_name, "ParallelWorkerMain");
 	worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(pcxt->seg));
-	worker.bgw_notify_pid = MyProcPid;
 
 	/*
 	 * Start workers.
@@ -768,16 +762,21 @@ WaitForParallelWorkersToAttach(ParallelContext *pcxt)
 			{
 				/*
 				 * Worker not yet started, so we must wait.  The postmaster
-				 * will notify us if the worker's state changes.  Our latch
-				 * 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 if the worker's state changes with
+				 * INTERRUPT_WAIT_WAKEUP.  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 = WaitLatch(MyLatch,
-							   WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-							   -1, WAIT_EVENT_BGWORKER_STARTUP);
+				rc = WaitInterrupt(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP,
+								   WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								   -1, WAIT_EVENT_BGWORKER_STARTUP);
 
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				if (rc & WL_INTERRUPT)
+				{
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+					CHECK_FOR_INTERRUPTS();
+				}
 			}
 		}
 
@@ -886,15 +885,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1035,21 +1035,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;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1084,9 +1069,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1328,7 +1311,10 @@ ParallelWorkerMain(Datum main_arg)
 	/* Set flag to indicate that we're initializing a parallel worker. */
 	InitializingParallelWorker = true;
 
-	/* Establish signal handlers. */
+	/*
+	 * Standard interrupt handlers have already been installed; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Determine and set our parallel worker number. */
@@ -1364,8 +1350,7 @@ ParallelWorkerMain(Datum main_arg)
 	fps = shm_toc_lookup(toc, PARALLEL_KEY_FIXED, false);
 	MyFixedParallelState = fps;
 
-	/* Arrange to signal the leader if we exit. */
-	ParallelLeaderPid = fps->parallel_leader_pid;
+	/* Arrange to send an interrupt to the leader if we exit. */
 	ParallelLeaderProcNumber = fps->parallel_leader_proc_number;
 	before_shmem_exit(ParallelWorkerShutdown, PointerGetDatum(seg));
 
@@ -1381,8 +1366,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
@@ -1621,9 +1605,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/slru.c b/src/backend/access/transam/slru.c
index 556edcbf7cb..4564204cd71 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 55b9f38927d..1d462c95b82 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 0b5ad4dc79c..e591eb5c2dd 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2640,7 +2640,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, walwriterProc);
 	}
 }
 
@@ -9497,11 +9497,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index ecb3c8c0820..66643281c0e 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -25,12 +25,12 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "utils/acl.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -736,17 +736,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 6d2c4a86b96..fca6f70af4d 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -410,7 +411,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -484,13 +484,6 @@ InitWalRecovery(ControlFileData *ControlFile, bool *wasShutdown_ptr,
 	readRecoverySignalFile();
 	validateRecoveryParameters();
 
-	/*
-	 * Take ownership of the wakeup latch if we're going to sleep during
-	 * recovery, if required.
-	 */
-	if (ArchiveRecoveryRequested)
-		OwnLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
-
 	/*
 	 * Set the WAL reading processor now, as it will be needed when reading
 	 * the checkpoint record required (backup_label or not).
@@ -1593,13 +1586,6 @@ ShutdownWalRecovery(void)
 		snprintf(recoveryPath, MAXPGPATH, XLOGDIR "/RECOVERYHISTORY");
 		unlink(recoveryPath);	/* ignore any error */
 	}
-
-	/*
-	 * We don't need the latch anymore. It's not strictly necessary to disown
-	 * it, but let's do it for the sake of tidiness.
-	 */
-	if (ArchiveRecoveryRequested)
-		DisownLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 }
 
 /*
@@ -1729,8 +1715,8 @@ PerformWalRecovery(void)
 			}
 #endif
 
-			/* Handle interrupt signals of startup process */
-			ProcessStartupProcInterrupts();
+			CHECK_FOR_INTERRUPTS();
+			StartupProcCheckPostmasterDeath();
 
 			/*
 			 * Pause WAL replay, if requested by a hot-standby session via
@@ -1759,8 +1745,8 @@ PerformWalRecovery(void)
 			}
 
 			/*
-			 * If we've been asked to lag the primary, wait on latch until
-			 * enough time has passed.
+			 * If we've been asked to lag the primary, wait until enough time
+			 * has passed.
 			 */
 			if (recoveryApplyDelay(xlogreader))
 			{
@@ -1781,9 +1767,8 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2917,7 +2902,7 @@ recoveryPausesHere(bool endOfRecovery)
 	/* loop until recoveryPauseState is set to RECOVERY_NOT_PAUSED */
 	while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED)
 	{
-		ProcessStartupProcInterrupts();
+		CHECK_FOR_INTERRUPTS();
 		if (CheckForStandbyTrigger())
 			return;
 
@@ -2994,8 +2979,8 @@ recoveryApplyDelay(XLogReaderState *record)
 	delayUntil = TimestampTzPlusMilliseconds(xtime, recovery_min_apply_delay);
 
 	/*
-	 * Exit without arming the latch if it's already past time to apply this
-	 * record
+	 * Exit without clearing the interrupt if it's already past time to apply
+	 * this record
 	 */
 	msecs = TimestampDifferenceMilliseconds(GetCurrentTimestamp(), delayUntil);
 	if (msecs <= 0)
@@ -3003,11 +2988,16 @@ recoveryApplyDelay(XLogReaderState *record)
 
 	while (true)
 	{
-		ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
-
 		/* This might change recovery_min_apply_delay. */
-		ProcessStartupProcInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
+		/*
+		 * Wait until more WAL arrives.  (We use a dedicated interrupt rather
+		 * than INTERRUPT_WAIT_WAKEUP 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).
+		 */
+		ClearInterrupt(INTERRUPT_WAL_ARRIVED);
 		if (CheckForStandbyTrigger())
 			break;
 
@@ -3028,10 +3018,12 @@ recoveryApplyDelay(XLogReaderState *record)
 
 		elog(DEBUG2, "recovery apply delay %ld milliseconds", msecs);
 
-		(void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 msecs,
-						 WAIT_EVENT_RECOVERY_APPLY_DELAY);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAL_ARRIVED |
+							 INTERRUPT_CHECK_PROMOTE,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 msecs,
+							 WAIT_EVENT_RECOVERY_APPLY_DELAY);
 	}
 	return true;
 }
@@ -3709,16 +3701,16 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 						/* Do background tasks that might benefit us later. */
 						KnownAssignedTransactionIdsIdleMaintenance();
 
-						(void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch,
-										 WL_LATCH_SET | WL_TIMEOUT |
-										 WL_EXIT_ON_PM_DEATH,
-										 wait_time,
-										 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
-						ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+						(void) WaitInterrupt(CheckForInterruptsMask |
+											 INTERRUPT_WAL_ARRIVED,
+											 WL_INTERRUPT | WL_TIMEOUT |
+											 WL_EXIT_ON_PM_DEATH,
+											 wait_time,
+											 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
+						ClearInterrupt(INTERRUPT_WAL_ARRIVED);
 						now = GetCurrentTimestamp();
 
-						/* Handle interrupt signals of startup process */
-						ProcessStartupProcInterrupts();
+						CHECK_FOR_INTERRUPTS();
 					}
 					last_fail_time = now;
 					currentSource = XLOG_FROM_ARCHIVE;
@@ -3984,11 +3976,12 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * Wait for more WAL to arrive, when we will be woken
 					 * immediately by the WAL receiver.
 					 */
-					(void) WaitLatch(&XLogRecoveryCtl->recoveryWakeupLatch,
-									 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-									 -1L,
-									 WAIT_EVENT_RECOVERY_WAL_STREAM);
-					ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+					(void) WaitInterrupt(CheckForInterruptsMask |
+										 INTERRUPT_WAL_ARRIVED,
+										 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+										 -1L,
+										 WAIT_EVENT_RECOVERY_WAL_STREAM);
+					ClearInterrupt(INTERRUPT_WAL_ARRIVED);
 					break;
 				}
 
@@ -4004,11 +3997,8 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 			RECOVERY_NOT_PAUSED)
 			recoveryPausesHere(false);
 
-		/*
-		 * This possibly-long loop needs to handle interrupts of startup
-		 * process.
-		 */
-		ProcessStartupProcInterrupts();
+		/* This possibly-long loop needs to handle interrupts. */
+		CHECK_FOR_INTERRUPTS();
 	}
 
 	return XLREAD_FAIL;			/* not reached */
@@ -4440,11 +4430,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4482,7 +4472,10 @@ CheckPromoteSignal(void)
 void
 WakeupRecovery(void)
 {
-	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+	ProcNumber	startupProc = pg_atomic_read_u32(&ProcGlobal->startupProc);
+
+	if (startupProc != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAL_ARRIVED, startupProc);
 }
 
 /*
@@ -4686,7 +4679,7 @@ RecoveryRequiresIntParameter(const char *param_name, int currValue, int minValue
 
 			while (GetRecoveryPauseState() != RECOVERY_NOT_PAUSED)
 			{
-				ProcessStartupProcInterrupts();
+				CHECK_FOR_INTERRUPTS();
 
 				if (CheckForStandbyTrigger())
 				{
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index bf4630677b4..dd81587aea6 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -69,7 +69,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -242,9 +242,9 @@ deleteLSNWaiter(WaitLSNType lsnType)
 #define	WAKEUP_PROC_STATIC_ARRAY_SIZE (16)
 
 /*
- * Remove waiters whose LSN has been reached from the heap and set their
- * latches.  If InvalidXLogRecPtr is given, remove all waiters from the heap
- * and set latches for all waiters.
+ * Remove waiters whose LSN has been reached from the heap and wake them up.
+ * If InvalidXLogRecPtr is given, remove all waiters from the heap and wake
+ * them all up.
  *
  * This function first accumulates waiters to wake up into an array, then
  * wakes them up without holding a WaitLSNLock.  The array size is static and
@@ -299,14 +299,13 @@ wakeupWaiters(WaitLSNType lsnType, XLogRecPtr currentLSN)
 		LWLockRelease(WaitLSNLock);
 
 		/*
-		 * Set latches for processes whose waited LSNs have been reached.
-		 * Since SetLatch() is a time-consuming operation, we do this outside
-		 * of WaitLSNLock. This is safe because procLatch is never freed, so
-		 * at worst we may set a latch for the wrong process or for no process
-		 * at all, which is harmless.
+		 * Wake up processes whose waited LSNs have been reached.  Since
+		 * SendInterrupt is a time-consuming operation, we do this outside of
+		 * WaitLSNLock. (If the backend exits or is no longer waiting, we'll
+		 * send spurious wakeup, but that's harmless)
 		 */
 		for (j = 0; j < numWakeUpProcs; j++)
-			SetLatch(&GetPGProcByNumber(wakeUpProcs[j])->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeUpProcs[j]);
 
 	} while (numWakeUpProcs == WAKEUP_PROC_STATIC_ARRAY_SIZE);
 }
@@ -365,7 +364,7 @@ WaitLSNTypeRequiresRecovery(WaitLSNType t)
 }
 
 /*
- * Wait using MyLatch till the given LSN is reached, the replica gets
+ * Wait using WaitInterrupt till the given LSN is reached, the replica gets
  * promoted, or the postmaster dies.
  *
  * Returns WAIT_LSN_RESULT_SUCCESS if target LSN was reached.
@@ -377,7 +376,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 {
 	XLogRecPtr	currentLSN;
 	TimestampTz endtime = 0;
-	int			wake_events = WL_LATCH_SET | WL_POSTMASTER_DEATH;
+	int			wake_events = WL_INTERRUPT | WL_POSTMASTER_DEATH;
 
 	/* Shouldn't be called when shmem isn't initialized */
 	Assert(waitLSNState);
@@ -435,8 +434,9 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch, wake_events, delay_ms,
-					   WaitLSNWaitEvents[lsnType]);
+		rc = WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						   wake_events, delay_ms,
+						   WaitLSNWaitEvents[lsnType]);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
@@ -448,8 +448,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index 4d8d90f356b..d76de034347 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -147,7 +147,7 @@ throttle(bbsink_throttle *sink, size_t increment)
 		(sink->throttling_counter / sink->throttling_sample);
 
 	/*
-	 * Since the latch could be set repeatedly because of concurrently WAL
+	 * Since the interrupt could be set repeatedly because of concurrently WAL
 	 * activity, sleep in a loop to ensure enough time has passed.
 	 */
 	for (;;)
@@ -164,22 +164,16 @@ throttle(bbsink_throttle *sink, size_t increment)
 		if (sleep <= 0)
 			break;
 
-		ResetLatch(MyLatch);
-
-		/* We're eating a potentially set latch, 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 = WaitLatch(MyLatch,
-								WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								(long) (sleep / 1000),
-								WAIT_EVENT_BASE_BACKUP_THROTTLE);
-
-		if (wait_result & WL_LATCH_SET)
-			CHECK_FOR_INTERRUPTS();
+		wait_result = WaitInterrupt(CheckForInterruptsMask,
+									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									(long) (sleep / 1000),
+									WAIT_EVENT_BASE_BACKUP_THROTTLE);
 
 		/* Done waiting? */
 		if (wait_result & WL_TIMEOUT)
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 0b6d119dad0..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,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
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,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
- *	  sets the process's latch, 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.
+ *	  main loop in PostgresBackend()).  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
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,15 +166,14 @@
 #include "commands/async.h"
 #include "common/hashfn.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -289,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -319,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * 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
@@ -528,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. 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;
 
@@ -553,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1266,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1361,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1387,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1619,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1639,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2243,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2263,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2294,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2305,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2331,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2341,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2361,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2372,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[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, signalProcnos[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);
 	}
 }
 
@@ -2531,39 +2496,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 */
-	SetLatch(MyLatch);
-}
-
 /*
  * 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
- *		via the process's latch, 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 the interrupt 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,
@@ -2572,12 +2510,13 @@ 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)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2687,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3051,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 84c8809a889..0a2b52c0806 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -271,9 +271,13 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 					/* Try to receive another message */
 					int			mtype;
 					int			maxmsglen;
+					bool		save_query_cancel_enabled;
 
 			readmessage:
-					HOLD_CANCEL_INTERRUPTS();
+					save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+					if (save_query_cancel_enabled)
+						DisableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					pq_startmsgread();
 					mtype = pq_getbyte();
 					if (mtype == EOF)
@@ -305,7 +309,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 62c1ebdfd9b..35b3a5f75ca 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2435,8 +2435,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2445,9 +2444,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 (InterruptPending(INTERRUPT_CONFIG_RELOAD) && AmAutoVacuumWorkerProcess())
 	{
-		ConfigReloadPending = false;
+		ClearInterrupt(INTERRUPT_CONFIG_RELOAD);
 		ProcessConfigFile(PGC_SIGHUP);
 		VacuumUpdateCosts();
 	}
@@ -2534,8 +2533,8 @@ vacuum_delay_point(bool is_analyze)
 		/*
 		 * We don't want to ignore postmaster death during very long vacuums
 		 * with vacuum_cost_delay configured.  We can't use the usual
-		 * WaitLatch() approach here because we want microsecond-based sleep
-		 * durations above.
+		 * WaitInterrupt() approach here because we want microsecond-based
+		 * sleep durations above.
 		 */
 		if (IsUnderPostmaster && !PostmasterIsAlive())
 			exit(1);
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 85c85569b5e..5368de170c3 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,10 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
+#include "utils/resowner.h"
 #include "utils/wait_event.h"
 
 /* Shared state for parallel-aware Append. */
@@ -1045,7 +1044,7 @@ ExecAppendAsyncEventWait(AppendState *node)
 	Assert(node->as_eventset == NULL);
 	node->as_eventset = CreateWaitEventSet(CurrentResourceOwner, nevents);
 	AddWaitEventToSet(node->as_eventset, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-					  NULL, NULL);
+					  0, NULL);
 
 	/* Give each waiting subplan a chance to add an event. */
 	i = -1;
@@ -1069,8 +1068,8 @@ ExecAppendAsyncEventWait(AppendState *node)
 	}
 
 	/*
-	 * Add the process latch to the set, so that we wake up to process the
-	 * standard interrupts with CHECK_FOR_INTERRUPTS().
+	 * Add the currently enabled interrupts to the set, so that we wake up to
+	 * process them with CHECK_FOR_INTERRUPTS().
 	 *
 	 * NOTE: For historical reasons, it's important that this is added to the
 	 * WaitEventSet after the ExecAsyncConfigureWait() calls.  Namely,
@@ -1080,8 +1079,8 @@ ExecAppendAsyncEventWait(AppendState *node)
 	 * we cannot change it now.  The pattern has possibly been copied to other
 	 * extensions too.
 	 */
-	AddWaitEventToSet(node->as_eventset, WL_LATCH_SET, PGINVALID_SOCKET,
-					  MyLatch, NULL);
+	AddWaitEventToSet(node->as_eventset, WL_INTERRUPT, PGINVALID_SOCKET,
+					  CheckForInterruptsMask, NULL);
 
 	/* Return at most EVENT_BUFFER_SIZE events in one call. */
 	if (nevents > EVENT_BUFFER_SIZE)
@@ -1124,14 +1123,10 @@ ExecAppendAsyncEventWait(AppendState *node)
 				ExecAsyncNotify(areq);
 			}
 		}
-
-		/* Handle standard interrupts */
-		if ((w->events & WL_LATCH_SET) != 0)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
 	}
+
+	/* Handle standard interrupts */
+	CHECK_FOR_INTERRUPTS();
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 114693abb32..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,9 +34,8 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
-#include "storage/latch.h"
 #include "utils/wait_event.h"
 
 
@@ -383,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..459f3d3cae4
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,260 @@
+Interrupts
+==========
+
+"Interrupts" are a set of flags that represent conditions that should
+be handled at a later time.  They are roughly analogous to Unix
+signals, except that they are handled cooperatively by checking for
+them at many points in the code. The CHECK_FOR_INTERRUPTS() macro is
+called at strategically located spots where it is normally safe to
+process interrupts.
+
+Well behaved backend code calls CHECK_FOR_INTERRUPTS() periodically in
+any long-running loops or other long computations, and never sleeps
+using mechanisms other than the WaitEventSet mechanism or the more
+convenient WaitInterrupt / WaitSockerOrInterrupt functions (except for
+bounded short periods, eg LWLock waits). Following these rules ensures
+that the backend doesn't become uncancellable for too long.
+
+Each interrupt type can have a handler function associated with it,
+which will be called by CHECK_FOR_INTERRUPTS() if the interrupt is
+pending.  A standard set of interrupt handlers are installed at
+process startup, but they can be overridden by
+SetInterruptHandler(). A handler can also be temporarily disabled by
+calling DisableInterrupt and re-enabled by EnableInterrupt.
+
+If an interrupt is raised that doesn't have a handler function or it
+is not enabled, the interrupt will remain pending until it is cleared,
+or the handler is re-enabled.
+
+If an interrupt is sent that was already pending for the target
+process, it is coalesced, ie. the target process will process it only
+once. Attempting to set an interrupt bit that is already set is very
+fast, amounting to merely an atomic op in shared memory.
+
+See src/include/ipc/interrupt.h for a list of all the interrupts used
+in core code. Extensions can define their own interrupts.
+
+HOLD/RESUME_INTERRUPTS
+----------------------
+
+Processing an interrupt may throw an error with ereport() (for
+example, query cancellation) or terminate the process gracefully. 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 interrupt processing.  The HOLD_INTERRUPTS() and
+RESUME_INTERRUPTS() macros allow code to ensure that no interrupt
+handlers are called, 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.
+
+HOLD_INTERRUPTS() blocks can be nested. If HOLD_INTERRUPTS() is called
+multiple times, interrupts are held until every HOLD_INTERRUPTS() call
+has been balanced with a RESUME_INTERRUPTS() call.
+
+Critical sections
+-----------------
+
+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! This mechanism is mostly used for XLOG-related code.
+
+Sending interrupts
+------------------
+
+Interrupt flags can be "raised" in different ways:
+- synchronously by code that wants to defer an action, by calling
+  RaiseInterrupt,
+- asynchronously by timers or other signal handlers, also by calling
+  RaiseInterrupt, or
+- "sent" by other backends with SendInterrupt
+
+Processing interrupts
+---------------------
+
+Interrupts are usually handled by handler functions, which are called
+from CHECK_FOR_INTERRUPTS() when the corresponding interrupt is
+pending. Use SetInterruptHandler() to install a handler function.
+
+Sometimes you don't want an interrupt to be processed at any random
+CHECK_FOR_INTERRUPTS() invocation. In that case, you can leave the
+interrupt handler disabled, and check for the interrupt explicitly at
+suitable spots with InterruptPending().
+
+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 CHECK_FOR_INTERRUPTS() is sure to clear the
+interrupt.
+
+
+Waiting for an interrupt
+------------------------
+The correct pattern to wait for an event(s) that raises an interrupt
+is:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	/* Check if our interrupt was raised (clearing it if it was) */
+	if (ConsumeInterrupt(INTERRUPT_PRINTER_ON_FIRE))
+		break;
+
+	/*
+	 * Sleep until our interrupt or one of the standard interrupts is
+	 * received.
+	 */
+	WaitInterrupt(INTERRUPT_CFI_MASK |
+				  INTERRUPT_PRINTER_ON_FIRE,
+				  ...);
+}
+```
+
+Often an interrupt is used to wake up a process that is known to be
+waiting and not doing anything else. The INTERRUPT_WAIT_WAKEUP
+interrupt is reserved for such use cases where you don't need a
+dedicated interrupt flag. When using INTERRUPT_WAIT_WAKEUP, you need
+some other signaling, e.g. a flag in shared memory, to indicate that
+the desired event has happen, as you may sometimes receive false
+wakeups. The pattern using INTERRUPT_WAIT_WAKEUP looks like this:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+}
+```
+
+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 ClearInterrupt() call, you will miss it and Wait will
+incorrectly block. Another valid coding pattern looks like:
+
+```
+for (;;)
+{
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+}
+```
+
+This is useful to reduce interrupt traffic if it's expected that the
+loop's termination condition will often be satisfied in the first
+iteration; the cost is an extra loop iteration before blocking when it
+is not.  What must be avoided is placing any checks for asynchronous
+events after WaitInterrupt and before ClearInterrupt, as that creates
+a race condition.
+
+Custom interrupts in extensions
+-------------------------------
+
+An extension can allocate interrupt bits for its own purposes by
+calling RequestAddinInterrupt(). Note that interrupts are a somewhat
+scarce resource, so consider using INTERRUPT_WAIT_WAKEUP if all you
+need is a simple wakeup in some loop.
+
+
+Unix Signals
+============
+
+Postmaster and most child processes also respond to a few standard
+Unix signals:
+
+SIGHUP -> Raises INTERRUPT_CONFIG_RELOAD
+SIGINT -> Raises INTERRUPT_QUERY_CANCEL
+SIGTERM -> Raises INTERRUPT_TERMINATE
+SIGQUIT -> immediate shutdown, abort the process
+
+pg_ctl uses these Unix signals to tell postmaster to reload config,
+stop, etc. These are also mentioned in the user documentation.
+
+Postmaster cannot send interrupts to processes without PGPROC entries
+(just syslogger nowadays), and it doesn't know the PGPROC entries of
+other child processes anyway. Hence, it still uses the above Unix
+signals for postmaster -> child signaling. The only exception is when
+postmaster notifies a backend that a bgworker it launched has
+exited. Postmaster sends that interrupt directly. The backend
+registers explicitly for that notification, and supplies the
+ProcNumber to postmaster when registering.
+
+There are a few more exceptions:
+
+- A few processes like the checkpointer, archiver, and IO workers
+  don't react to SIGTERM, because Unix system shutdown sends a SIGTERM
+  to all processes, and we want them to exit only only after all other
+  process. Instead, the postmaster sends them SIGUSR2 after all the
+  other processes have exited.
+
+- Child processes use SIGUSR1 to request the postmaster to do various
+  actions or notify that some event has occurred. See pmsignal.c for
+  details on that mechanism
+
+
+TODO: Open questions
+====================
+
+TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
+----------------------------------------------------
+
+Postmaster cannot send interrupts to processes without PGPROC
+entries. Hence, it still uses plain Unix signals. Would be nice if
+postmaster could send interrupts directly. If we moved the interrupt
+mask to PMChildSlot, it could.  However, PGPROC seems like a more
+natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+d) Keep it as it is, continue to use signals for postmaster -> child
+   signaling
+
+
+TODO: Unique session id
+----------------------
+
+In some places, we read the pid of a process (from PGPROC or
+elsewhere), and send signal to it, accepting that the process may have
+already exited.  If we directly replace those uses of 'pid' with a
+ProcNumber, the ProcNumber might get reused much faster than the pid
+would. Solutions:
+
+a) Introduce the concept of a unique session ID that is never recycled
+   (or not for a long time, anyway). When reading the ProcNumber to
+   send an interrupt to, also read the session ID. When sending the
+   interrupt, check that the session ID matches.
+
+
+TODO: Other
+-----------
+
+- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
+  it's nevertheless more instructions than it used to be.
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
new file mode 100644
index 00000000000..61d148409bc
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,431 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint64 LocalPendingInterrupts;
+
+pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
+
+/*
+ * Install an interrupt handler callback function for the given interrupt.
+ *
+ * You need to also enable the interrupt with EnableInterrupt(), unless you're
+ * replacing an existing handler function.
+ */
+void
+SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler)
+{
+	/*
+	 * XXX: It's somewhat inefficient to loop through all the bits, but this
+	 * isn't performance critical.
+	 */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+		{
+			/* Replace old handler */
+			interrupt_handlers[i] = handler;
+		}
+	}
+}
+
+/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
+void
+EnableInterrupt(InterruptMask interruptMask)
+{
+#ifdef USE_ASSERT_CHECKING
+	/* Check that the interrupt has a handler defined */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * 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 call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning, if it was set when entering.  (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)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/* Check once what interrupts are pending */
+	interruptsToProcess =
+		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		{
+			/*
+			 * Clear the interrupt *before* calling the handler function, so
+			 * that if the interrupt is received again while the handler
+			 * function is being executed, we won't miss it.
+			 *
+			 * For similar reasons, we also clear the flags one by one even if
+			 * multiple interrupts are pending.  Otherwise if one of the
+			 * interrupt handlers bail out with an ERROR, we would have
+			 * already cleared the other bits, and would miss processing them.
+			 */
+			ClearInterrupt(UINT64_BIT(i));
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint64		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint64		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in some aux processes that
+ * want to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
+
+/* Reserve an interrupt bit for use in an extension */
+InterruptMask
+RequestAddinInterrupt(void)
+{
+	InterruptMask result;
+
+	if (nextAddinInterruptBit == END_ADDIN_INTERRUPTS)
+		elog(ERROR, "out of addin interrupt bits");
+
+	result = UINT64_BIT(nextAddinInterruptBit);
+	nextAddinInterruptBit++;
+	return result;
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..589f755622a
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 *
+	 * The process may ignore the interrupts that these raise, e.g if query
+	 * cancellation is not applicable.  But there's no harm in having the
+	 * signal handlers in place anyway.
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is sent by postmaster to some aux processes, for different
+	 * purposes.  Such processes override this before unblocking signals, but
+	 * ignore it by default.
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/*
+	 * SIGALRM is used for timeouts, but the handler is established later in
+	 * InitializeTimeouts()
+	 */
+	pqsignal(SIGALRM, SIG_IGN);
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * 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 INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e04aa2e68ed..f8fbdd090d4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 540ed62a5cc..3ddc6371c5d 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,13 +15,13 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
-#include "storage/latch.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/wait_event.h"
@@ -423,7 +423,7 @@ be_gssapi_read(Port *port, void *ptr, size_t len)
 
 /*
  * Read the specified number of bytes off the wire, waiting using
- * WaitLatchOrSocket if we would block.
+ * WaitInterruptOrSocket if we would block.
  *
  * Results are read into PqGSSRecvBuffer.
  *
@@ -459,9 +459,8 @@ read_or_wait(Port *port, ssize_t len)
 		 */
 		if (ret <= 0)
 		{
-			WaitLatchOrSocket(NULL,
-							  WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
-							  port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+			WaitInterruptOrSocket(0, WL_SOCKET_READABLE | WL_EXIT_ON_PM_DEATH,
+								  port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
 
 			/*
 			 * If we got back zero bytes, and then waited on the socket to be
@@ -498,7 +497,7 @@ read_or_wait(Port *port, ssize_t len)
  *
  * Note that unlike the be_gssapi_read/be_gssapi_write functions, this
  * function WILL block on the socket to be ready for read/write (using
- * WaitLatchOrSocket) as appropriate while establishing the GSSAPI
+ * WaitInterruptOrSocket) as appropriate while establishing the GSSAPI
  * session.
  */
 ssize_t
@@ -680,9 +679,8 @@ secure_open_gssapi(Port *port)
 				/* Wait and retry if we couldn't write yet */
 				if (ret <= 0)
 				{
-					WaitLatchOrSocket(NULL,
-									  WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH,
-									  port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
+					WaitInterruptOrSocket(0, WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH,
+										  port->sock, 0, WAIT_EVENT_GSS_OPEN_SERVER);
 					continue;
 				}
 
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 14c6532bb16..ef641fca3f2 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/wait_event.h"
@@ -534,8 +535,8 @@ aloop:
 				else
 					waitfor = WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH;
 
-				(void) WaitLatchOrSocket(NULL, waitfor, port->sock, 0,
-										 WAIT_EVENT_SSL_OPEN_SERVER);
+				(void) WaitInterruptOrSocket(0, waitfor, port->sock, 0,
+											 WAIT_EVENT_SSL_OPEN_SERVER);
 				goto aloop;
 			case SSL_ERROR_SYSCALL:
 				if (r < 0 && errno != 0)
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edd69823b92..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,9 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -175,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -182,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -214,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -229,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -256,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -285,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -301,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -308,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -339,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -350,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -365,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 4a442f22df6..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,12 +73,11 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
 
@@ -176,7 +175,7 @@ pq_init(ClientSocket *client_sock)
 {
 	Port	   *port;
 	int			socket_pos PG_USED_FOR_ASSERTS_ONLY;
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
 
 	/* allocate the Port struct and copy the ClientSocket contents to it */
 	port = palloc0_object(Port);
@@ -288,8 +287,8 @@ pq_init(ClientSocket *client_sock)
 
 	/*
 	 * In backends (as soon as forked) we operate the underlying socket in
-	 * nonblocking mode and use latches to implement blocking semantics if
-	 * needed. That allows us to provide safely interruptible reads and
+	 * nonblocking mode and use WaitEventSet to implement blocking semantics
+	 * if needed. That allows us to provide safely interruptible reads and
 	 * writes.
 	 */
 #ifndef WIN32
@@ -307,18 +306,18 @@ pq_init(ClientSocket *client_sock)
 
 	FeBeWaitSet = CreateWaitEventSet(NULL, FeBeWaitSetNEvents);
 	socket_pos = AddWaitEventToSet(FeBeWaitSet, WL_SOCKET_WRITEABLE,
-								   port->sock, NULL, NULL);
-	latch_pos = AddWaitEventToSet(FeBeWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
+								   port->sock, 0, NULL);
+	interrupt_pos = AddWaitEventToSet(FeBeWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  CheckForInterruptsMask, NULL);
 	AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-					  NULL, NULL);
+					  0, NULL);
 
 	/*
 	 * The event positions match the order we added them, but let's sanity
 	 * check them to be sure.
 	 */
 	Assert(socket_pos == FeBeWaitSetSocketPos);
-	Assert(latch_pos == FeBeWaitSetLatchPos);
+	Assert(interrupt_pos == FeBeWaitSetInterruptPos);
 
 	return port;
 }
@@ -1412,8 +1411,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;
 		}
 
@@ -2063,25 +2061,14 @@ pq_check_connection(void)
 	 * It's OK to modify the socket event filter without restoring, because
 	 * all FeBeWaitSet socket wait sites do the same.
 	 */
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, WL_SOCKET_CLOSED, NULL);
+	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_LATCH_SET)
-		{
-			/*
-			 * A latch event might be preventing other events from being
-			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect latches to survive across
-			 * CHECK_FOR_INTERRUPTS().
-			 */
-			ResetLatch(MyLatch);
-			goto retry;
-		}
 	}
 
 	return true;
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index 22e5164adbf..4af48671552 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,20 +14,19 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/wait_event.h"
 
 static shm_mq_handle *pq_mq_handle = NULL;
 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);
@@ -77,14 +76,13 @@ pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg)
 }
 
 /*
- * Arrange to SendProcSignal() to the parallel leader each time we transmit
+ * Arrange to send an 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;
 }
 
@@ -171,27 +169,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		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);
-			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) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 4f5292d8f88..a438a2b575d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e8180634db5..b4c66bb45ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -27,15 +27,17 @@
  * launcher has set up.
  *
  * If the fork() call fails in the postmaster, it sets a flag in the shared
- * memory area, and sends a signal to the launcher.  The launcher, upon
- * noticing the flag, can try starting the worker again by resending the
+ * memory area, sends a signal to the launcher, raising the
+ * INTERRUPT_AUTOVACUUM_WORKER_FINISHED interrupt. The launcher, upon
+ * noticing the interrupt, can try starting the worker again by resending the
  * signal.  Note that the failure can only be transient (fork failure due to
  * high load, memory pressure, too many processes, etc); more permanent
  * problems, like failure to connect to a database, are detected later in the
  * worker and dealt with just by having the worker exit normally.  The launcher
  * will launch a new worker again later, per schedule.
  *
- * When the worker is done vacuuming it sends SIGUSR2 to the launcher.  The
+ * When the worker is done vacuuming and exits, the postmaster sends SIGUSR2
+ * to the launcher, raising INTERRUPT_AUTOVACUUM_WORKER_FINISHED.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -152,9 +154,6 @@ int			Log_autoanalyze_min_duration = 600000;
 static double av_storage_param_cost_delay = -1;
 static int	av_storage_param_cost_limit = -1;
 
-/* Flags set by signal handlers */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-
 /* Comparison points for determining whether freeze_max_age is exceeded */
 static TransactionId recentXid;
 static MultiXactId recentMulti;
@@ -288,6 +287,9 @@ typedef struct AutoVacuumWorkItem
  *
  * This struct is protected by AutovacuumLock, except for av_signal and parts
  * of the worker list (see above).
+ *
+ * XXX: av_signal could be replaced with individual interrupts. This works
+ * too, though.
  *-------------
  */
 typedef struct
@@ -323,7 +325,7 @@ avl_dbase  *avl_dbase_array;
 static WorkerInfo MyWorkerInfo = NULL;
 
 static Oid	do_start_worker(void);
-static void ProcessAutoVacLauncherInterrupts(void);
+static void ProcessAutoVacLauncherConfigReloadInterrupt(void);
 pg_noreturn static void AutoVacLauncherShutdown(void);
 static void launcher_determine_sleep(bool canlaunch, bool recursing,
 									 struct timeval *nap);
@@ -395,21 +397,23 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * Set up signal handlers.  We operate on databases much like a regular
-	 * backend, so we use the same signal handling.  See equivalent code in
-	 * tcop/postgres.c.
+	 * Set up signal and interrupt handlers.  We operate on databases much
+	 * like a regular backend, so we use mostly the same handling.  See
+	 * equivalent code in tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
+	SetStandardInterruptHandlers();
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Postmaster uses SIGUSR2 to raise INTERRUPT_AUTOVACUUM_WORKER_FINISHED */
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
+
+	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -456,7 +460,8 @@ 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();
@@ -498,7 +503,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 (InterruptPending(INTERRUPT_TERMINATE))
 			AutoVacLauncherShutdown();
 
 		/*
@@ -557,7 +562,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (!AutoVacuumingActive())
 	{
-		if (!ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_TERMINATE))
 			do_start_worker();
 		proc_exit(0);			/* done */
 	}
@@ -572,41 +577,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -742,21 +750,17 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	AutoVacLauncherShutdown();
 }
 
-/*
- * Process any new interrupts.
- */
 static void
-ProcessAutoVacLauncherInterrupts(void)
+ProcessAutoVacLauncherConfigReloadInterrupt(void)
 {
 	/* the normal shutdown case */
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		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? */
@@ -774,17 +778,6 @@ ProcessAutoVacLauncherInterrupts(void)
 		/* rebuild the list in case the naptime changed */
 		rebuild_database_list(InvalidOid);
 	}
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	/* Process sinval catchup interrupts that happened while sleeping */
-	ProcessCatchupInterrupt();
 }
 
 /*
@@ -1351,8 +1344,8 @@ launch_worker(TimestampTz now)
 
 /*
  * Called from postmaster to signal a failure to fork a process to become
- * worker.  The postmaster should kill(SIGUSR2) the launcher shortly
- * after calling this function.
+ * worker.  The postmaster should send INTERRUPT_AUTOVACUUM_WORKER_FINISHED to
+ * the launcher shortly after calling this function.
  */
 void
 AutoVacWorkerFailed(void)
@@ -1364,8 +1357,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1400,22 +1392,18 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
+	SetStandardInterruptHandlers();
 
 	/*
-	 * SIGINT is used to signal canceling the current table's vacuum; SIGTERM
-	 * means abort and exit cleanly, and SIGQUIT means abandon ship.
+	 * INTERRUPT_QUERY_CANCEL (SIGINT) is used to cancel the current table's
+	 * vacuum; INTERRUPT_TERMINAT (SIGTERM) means abort and exit cleanly as
+	 * usual.
 	 */
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1546,12 +1534,7 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		/* wake up the launcher */
 		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
 		if (launcherProc != INVALID_PROC_NUMBER)
-		{
-			int			pid = GetPGProcByNumber(launcherProc)->pid;
-
-			if (pid != 0)
-				kill(pid, SIGUSR2);
-		}
+			SendInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED, launcherProc);
 	}
 	else
 	{
@@ -2293,9 +2276,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);
 
 			/*
@@ -2453,7 +2435,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();
 		{
@@ -2542,9 +2524,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();
 		}
@@ -2666,7 +2647,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/auxprocess.c b/src/backend/postmaster/auxprocess.c
index cf24f662d27..ae577859d99 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 #include "utils/wait_event.h"
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 8ff6a88a141..c3a199e8864 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -80,6 +80,8 @@ typedef struct BackgroundWorkerSlot
 	pid_t		pid;			/* InvalidPid = not started yet; 0 = dead */
 	uint64		generation;		/* incremented when slot is recycled */
 	BackgroundWorker worker;
+	int			notify_pmchild;
+	ProcNumber	notify_proc_number;
 } BackgroundWorkerSlot;
 
 /*
@@ -203,8 +205,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -245,6 +248,19 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	/*
+	 * FIXME: be extra paranoid and sanity check the proc number, since this
+	 * runs in the postmaster
+	 */
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -326,20 +342,20 @@ BackgroundWorkerStateChange(bool allow_new_workers)
 		/*
 		 * If the worker is marked for termination, we don't need to add it to
 		 * the registered workers list; we can just free the slot. However, if
-		 * bgw_notify_pid is set, the process that registered the worker may
-		 * need to know that we've processed the terminate request, so be sure
-		 * to signal it.
+		 * bgw_notify_proc_number is set, the process that registered the
+		 * worker may need to know that we've processed the terminate request,
+		 * so be sure to signal it.
 		 */
 		if (slot->terminate)
 		{
-			int			notify_pid;
+			ProcNumber	notify_proc_number;
 
 			/*
 			 * We need a memory barrier here to make sure that the load of
-			 * bgw_notify_pid and the update of parallel_terminate_count
-			 * complete before the store to in_use.
+			 * bgw_notify_proc_number and the update of
+			 * parallel_terminate_count complete before the store to in_use.
 			 */
-			notify_pid = slot->worker.bgw_notify_pid;
+			notify_proc_number = slot->notify_proc_number;
 			if ((slot->worker.bgw_flags & BGWORKER_CLASS_PARALLEL) != 0)
 				BackgroundWorkerData->parallel_terminate_count++;
 			slot->pid = 0;
@@ -347,8 +363,8 @@ BackgroundWorkerStateChange(bool allow_new_workers)
 			pg_memory_barrier();
 			slot->in_use = false;
 
-			if (notify_pid != 0)
-				kill(notify_pid, SIGUSR1);
+			if (notify_proc_number != INVALID_PROC_NUMBER)
+				SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 			continue;
 		}
@@ -394,23 +410,6 @@ BackgroundWorkerStateChange(bool allow_new_workers)
 		rw->rw_worker.bgw_main_arg = slot->worker.bgw_main_arg;
 		memcpy(rw->rw_worker.bgw_extra, slot->worker.bgw_extra, BGW_EXTRALEN);
 
-		/*
-		 * Copy the PID to be notified about state changes, but only if the
-		 * postmaster knows about a backend with that PID.  It isn't an error
-		 * if the postmaster doesn't know about the PID, because the backend
-		 * that requested the worker could have died (or been killed) just
-		 * after doing so.  Nonetheless, at least until we get some experience
-		 * with how this plays out in the wild, log a message at a relative
-		 * high debug level.
-		 */
-		rw->rw_worker.bgw_notify_pid = slot->worker.bgw_notify_pid;
-		if (!PostmasterMarkPIDForWorkerNotify(rw->rw_worker.bgw_notify_pid))
-		{
-			elog(DEBUG1, "worker notification PID %d is not valid",
-				 (int) rw->rw_worker.bgw_notify_pid);
-			rw->rw_worker.bgw_notify_pid = 0;
-		}
-
 		/* Initialize postmaster bookkeeping. */
 		rw->rw_pid = 0;
 		rw->rw_crashed_at = 0;
@@ -432,7 +431,7 @@ BackgroundWorkerStateChange(bool allow_new_workers)
  * NOTE: The entry is unlinked from BackgroundWorkerList.  If the caller is
  * iterating through it, better use a mutable iterator!
  *
- * Caller is responsible for notifying bgw_notify_pid, if appropriate.
+ * Caller is responsible for notifying bgw_notify_proc_number, if appropriate.
  *
  * This function must be invoked only in the postmaster.
  */
@@ -477,8 +476,8 @@ ReportBackgroundWorkerPID(RegisteredBgWorker *rw)
 	slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
 	slot->pid = rw->rw_pid;
 
-	if (rw->rw_worker.bgw_notify_pid != 0)
-		kill(rw->rw_worker.bgw_notify_pid, SIGUSR1);
+	if (slot->notify_proc_number != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, slot->notify_proc_number);
 }
 
 /*
@@ -494,12 +493,12 @@ void
 ReportBackgroundWorkerExit(RegisteredBgWorker *rw)
 {
 	BackgroundWorkerSlot *slot;
-	int			notify_pid;
+	ProcNumber	notify_proc_number;
 
 	Assert(rw->rw_shmem_slot < max_worker_processes);
 	slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
 	slot->pid = rw->rw_pid;
-	notify_pid = rw->rw_worker.bgw_notify_pid;
+	notify_proc_number = slot->notify_proc_number;
 
 	/*
 	 * If this worker is slated for deregistration, do that before notifying
@@ -512,27 +511,34 @@ ReportBackgroundWorkerExit(RegisteredBgWorker *rw)
 		rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 		ForgetBackgroundWorker(rw);
 
-	if (notify_pid != 0)
-		kill(notify_pid, SIGUSR1);
+	if (notify_proc_number != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 }
 
 /*
- * Cancel SIGUSR1 notifications for a PID belonging to an exiting backend.
+ * Cancel notifications for a bgworker belonging to an exiting backend.
  *
  * This function should only be called from the postmaster.
  */
 void
-BackgroundWorkerStopNotifications(pid_t pid)
+BackgroundWorkerStopNotifications(int pmchild)
 {
 	dlist_iter	iter;
 
 	dlist_foreach(iter, &BackgroundWorkerList)
 	{
 		RegisteredBgWorker *rw;
+		BackgroundWorkerSlot *slot;
 
 		rw = dlist_container(RegisteredBgWorker, rw_lnode, iter.cur);
-		if (rw->rw_worker.bgw_notify_pid == pid)
-			rw->rw_worker.bgw_notify_pid = 0;
+		Assert(rw->rw_shmem_slot < max_worker_processes);
+		slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+		if (slot->notify_pmchild == pmchild)
+		{
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
+		}
 	}
 }
 
@@ -564,14 +570,14 @@ ForgetUnstartedBackgroundWorkers(void)
 
 		/* If it's not yet started, and there's someone waiting ... */
 		if (slot->pid == InvalidPid &&
-			rw->rw_worker.bgw_notify_pid != 0)
+			slot->notify_proc_number != INVALID_PROC_NUMBER)
 		{
 			/* ... then zap it, and notify the waiter */
-			int			notify_pid = rw->rw_worker.bgw_notify_pid;
+			ProcNumber	notify_proc_number = slot->notify_proc_number;
 
 			ForgetBackgroundWorker(rw);
-			if (notify_pid != 0)
-				kill(notify_pid, SIGUSR1);
+			if (notify_proc_number != INVALID_PROC_NUMBER)
+				SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 		}
 	}
 }
@@ -625,11 +631,6 @@ ResetBackgroundWorkerCrashTimes(void)
 			 */
 			rw->rw_crashed_at = 0;
 			rw->rw_pid = 0;
-
-			/*
-			 * If there was anyone waiting for it, they're history.
-			 */
-			rw->rw_worker.bgw_notify_pid = 0;
 		}
 	}
 }
@@ -757,32 +758,28 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Set up signal handlers.
 	 */
+	SetStandardInterruptHandlers();
 	if (worker->bgw_flags & BGWORKER_BACKEND_DATABASE_CONNECTION)
 	{
 		/*
-		 * SIGINT is used to signal canceling the current action
+		 * Enable query cancellation by default. The background worker may
+		 * disable for more control on when cancellations are accepted.
 		 */
-		pqsignal(SIGINT, StatementCancelHandler);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+		SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+		/* like in regular backends */
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/* XXX Any other handlers needed here? */
 	}
 	else
 	{
+		/* no cancellation in backends that don't run queries */
 		pqsignal(SIGINT, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -988,15 +985,6 @@ RegisterBackgroundWorker(BackgroundWorker *worker)
 	if (!SanityCheckBackgroundWorker(worker, LOG))
 		return;
 
-	if (worker->bgw_notify_pid != 0)
-	{
-		ereport(LOG,
-				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("background worker \"%s\": only dynamic background workers can request notification",
-						worker->bgw_name)));
-		return;
-	}
-
 	/*
 	 * Enforce maximum number of workers.  Note this is overly restrictive: we
 	 * could allow more non-shmem-connected workers, because these don't count
@@ -1112,6 +1100,25 @@ RegisterDynamicBackgroundWorker(BackgroundWorker *worker,
 			if (parallel)
 				BackgroundWorkerData->parallel_register_count++;
 
+			if ((slot->worker.bgw_flags & BGWORKER_SHMEM_ACCESS) != 0 &&
+				(slot->worker.bgw_flags & BGWORKER_NO_NOTIFY) == 0)
+			{
+				/*
+				 * Set notify_proc_number so that postmaster will send us an
+				 * interrupt. Also remember the pmchild slot number;
+				 * postmaster needs it to detect when we exit, to disarm the
+				 * notification.
+				 */
+				slot->notify_pmchild = MyPMChildSlot;
+				slot->notify_proc_number = MyProcNumber;
+			}
+			else
+			{
+				/* No notifications. */
+				slot->notify_pmchild = 0;
+				slot->notify_proc_number = INVALID_PROC_NUMBER;
+			}
+
 			/*
 			 * Make sure postmaster doesn't see the slot as in use before it
 			 * sees the new contents.
@@ -1212,8 +1219,9 @@ GetBackgroundWorkerPid(BackgroundWorkerHandle *handle, pid_t *pidp)
  * BGWH_POSTMASTER_DIED, since it that case we know that startup will not
  * take place.
  *
- * The caller *must* have set our PID as the worker's bgw_notify_pid,
- * else we will not be awoken promptly when the worker's state changes.
+ * This works only if the worker was registered with BGWORKER_SHMEM_ACCESS and
+ * without BGWORKER_NO_NOTIFY, else we will not be awoken promptly when the
+ * worker's state changes.
  */
 BgwHandleStatus
 WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
@@ -1233,9 +1241,9 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 		if (status != BGWH_NOT_YET_STARTED)
 			break;
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
-					   WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1243,7 +1251,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1257,8 +1265,9 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
  * up and return BGWH_POSTMASTER_DIED, because it's the postmaster that
  * notifies us when a worker's state changes.
  *
- * The caller *must* have set our PID as the worker's bgw_notify_pid,
- * else we will not be awoken promptly when the worker's state changes.
+ * This works only if the worker was registered with BGWORKER_SHMEM_ACCESS and
+ * without BGWORKER_NO_NOTIFY, else we will not be awoken promptly when the
+ * worker's state changes.
  */
 BgwHandleStatus
 WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
@@ -1276,9 +1285,9 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 		if (status == BGWH_STOPPED)
 			break;
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_POSTMASTER_DEATH, 0,
-					   WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1286,7 +1295,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index efd041832a8..62895830071 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -98,16 +99,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * Set up interrupt handling
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -221,9 +217,9 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		int			rc;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		ProcessMainLoopInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do one cycle of dirty-buffer writing.
@@ -296,22 +292,22 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		 * will call it every BgWriterDelay msec.  While it's not critical for
 		 * correctness that that be exact, the feedback loop might misbehave
 		 * if we stray too far from that.  Hence, avoid loading this process
-		 * down with latch events that are likely to happen frequently during
-		 * normal operation.
+		 * down with interrupt events that are likely to happen frequently
+		 * during normal operation.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);
 
 		/*
-		 * If no latch event and BgBufferSync says nothing's happening, extend
+		 * If no interrupt and BgBufferSync says nothing's happening, extend
 		 * the sleep in "hibernation" mode, where we sleep for much longer
 		 * than bgwriter_delay says.  Fewer wakeups save electricity.  When a
-		 * backend starts using buffers again, it will wake us up by setting
-		 * our latch.  Because the extra sleep will persist only as long as no
-		 * buffer allocations happen, this should not distort the behavior of
-		 * BgBufferSync's control loop too badly; essentially, it will think
-		 * that the system-wide idle interval didn't exist.
+		 * backend starts using buffers again, it will wake us up by sending
+		 * us an interrupt.  Because the extra sleep will persist only as long
+		 * as no buffer allocations happen, this should not distort the
+		 * behavior of BgBufferSync's control loop too badly; essentially, it
+		 * will think that the system-wide idle interval didn't exist.
 		 *
 		 * There is a race condition here, in that a backend might allocate a
 		 * buffer between the time BgBufferSync saw the alloc count as zero
@@ -326,10 +322,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 			/* Ask for notification at next buffer allocation */
 			StrategyNotifyBgWriter(MyProcNumber);
 			/* Sleep ... */
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 BgWriterDelay * HIBERNATE_FACTOR,
-							 WAIT_EVENT_BGWRITER_HIBERNATE);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 BgWriterDelay * HIBERNATE_FACTOR,
+								 WAIT_EVENT_BGWRITER_HIBERNATE);
 			/* Reset the notification request in case we timed out */
 			StrategyNotifyBgWriter(-1);
 		}
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 98c2933a822..a9f4414fa01 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -11,8 +11,10 @@
  * condition.)
  *
  * The normal termination sequence is that checkpointer is instructed to
- * execute the shutdown checkpoint by SIGINT.  After that checkpointer waits
- * to be terminated via SIGUSR2, which instructs the checkpointer to exit(0).
+ * execute the shutdown checkpoint by SIGINT, which raises the
+ * INTERRUPT_SHUTDOWN_XLOG interrupt.  After that checkpointer waits
+ * to be terminated via SIGUSR2, raising INTERRUP_TERMINATE, which instructs
+ * the checkpointer to exit(0).
  * All backends must be stopped before SIGINT or SIGUSR2 is issued!
  *
  * Emergency termination is by SIGQUIT; like any backend, the checkpointer
@@ -44,13 +46,14 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -86,10 +89,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_WAIT_WAKEUP 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
@@ -164,7 +168,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;
@@ -176,7 +179,7 @@ static pg_time_t last_xlog_switch_time;
 
 /* Prototypes for private functions */
 
-static void ProcessCheckpointerInterrupts(void);
+static void ProcessCheckpointerConfigReloadInterrupt(void);
 static void CheckArchiveTimeout(void);
 static bool IsCheckpointOnSchedule(double progress);
 static bool FastCheckpointRequested(void);
@@ -206,22 +209,28 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	CheckpointerShmem->checkpointer_pid = MyProcPid;
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * Note: we deliberately ignore SIGTERM, because during a standard Unix
-	 * system shutdown cycle, init will SIGTERM all processes at once.  We
-	 * want to wait for the backends to exit, whereupon the postmaster will
-	 * tell us it's okay to shut down (via SIGUSR2).
+	 * Ignore SIGTERM, because during a standard Unix system shutdown cycle,
+	 * init will SIGTERM all processes at once.  We want to wait for the
+	 * backends to exit, whereupon the postmaster will tell us it's okay to
+	 * shut down (via INTERRUPT_SHUTDOWN_XLOG).
+	 */
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessCheckpointerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
+	/* we will check for INTERRUPT_TERMINATE explicitly in the loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
@@ -360,15 +369,15 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 		bool		chkpt_or_rstpt_timed = false;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Process any requests or signals received recently.
+		 * Process any requests or interrupts received recently.
 		 */
 		AbsorbSyncRequests();
 
-		ProcessCheckpointerInterrupts();
-		if (ShutdownXLOGPending || ShutdownRequestPending)
+		CHECK_FOR_INTERRUPTS();
+		if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG) || InterruptPending(INTERRUPT_TERMINATE))
 			break;
 
 		/*
@@ -542,10 +551,10 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 
 			/*
 			 * We may have received an interrupt during the checkpoint and the
-			 * latch might have been reset (e.g. in CheckpointWriteDelay).
+			 * interrupt might have been reset (e.g. in CheckpointWriteDelay).
 			 */
-			ProcessCheckpointerInterrupts();
-			if (ShutdownXLOGPending || ShutdownRequestPending)
+			CHECK_FOR_INTERRUPTS();
+			if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG) || InterruptPending(INTERRUPT_TERMINATE))
 				break;
 		}
 
@@ -570,7 +579,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);
@@ -586,10 +595,13 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 			cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
 		}
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout * 1000L /* convert to ms */ ,
-						 WAIT_EVENT_CHECKPOINTER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_SHUTDOWN_XLOG |
+							 INTERRUPT_TERMINATE,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout * 1000L /* convert to ms */ ,
+							 WAIT_EVENT_CHECKPOINTER_MAIN);
 	}
 
 	/*
@@ -598,7 +610,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -616,7 +628,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);
 	}
 
 	/*
@@ -626,55 +638,38 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessCheckpointerInterrupts();
-
-		if (ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_TERMINATE))
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-						 0,
-						 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_TERMINATE,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
 	}
 
 	/* Normal exit from the checkpointer is here */
 	proc_exit(0);				/* done */
 }
 
-/*
- * Process any new interrupts.
- */
 static void
-ProcessCheckpointerInterrupts(void)
+ProcessCheckpointerConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
+	ProcessConfigFile(PGC_SIGHUP);
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-
-		/*
-		 * Checkpointer is the last process to shut down, so we ask it to hold
-		 * the keys for a range of other tasks required most of which have
-		 * nothing to do with checkpointing at all.
-		 *
-		 * For various reasons, some config values can change dynamically so
-		 * the primary copy of them is held in shared memory to make sure all
-		 * backends see the same value.  We make Checkpointer responsible for
-		 * updating the shared memory copy if the parameter setting changes
-		 * because of SIGHUP.
-		 */
-		UpdateSharedMemoryConfig();
-	}
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
+	/*
+	 * Checkpointer is the last process to shut down, so we ask it to hold the
+	 * keys for a range of other tasks required most of which have nothing to
+	 * do with checkpointing at all.
+	 *
+	 * For various reasons, some config values can change dynamically so the
+	 * primary copy of them is held in shared memory to make sure all backends
+	 * see the same value.  We make Checkpointer responsible for updating the
+	 * shared memory copy if the parameter setting changes because of SIGHUP.
+	 */
+	UpdateSharedMemoryConfig();
 }
 
 /*
@@ -786,24 +781,18 @@ CheckpointWriteDelay(int flags, double progress)
 	if (!AmCheckpointerProcess())
 		return;
 
+	CHECK_FOR_INTERRUPTS();
+
 	/*
 	 * Perform the usual duties and take a nap, unless we're behind schedule,
 	 * in which case we just try to catch up as quickly as possible.
 	 */
 	if (!(flags & CHECKPOINT_FAST) &&
-		!ShutdownXLOGPending &&
-		!ShutdownRequestPending &&
+		!InterruptPending(INTERRUPT_SHUTDOWN_XLOG) &&
+		!InterruptPending(INTERRUPT_TERMINATE) &&
 		!FastCheckpointRequested() &&
 		IsCheckpointOnSchedule(progress))
 	{
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
-			/* update shmem copies of config variables */
-			UpdateSharedMemoryConfig();
-		}
-
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 
@@ -818,10 +807,12 @@ CheckpointWriteDelay(int flags, double progress)
 		 * Checkpointer and bgwriter are no longer related so take the Big
 		 * Sleep.
 		 */
-		WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
-				  100,
-				  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
-		ResetLatch(MyLatch);
+		WaitInterrupt(CheckForInterruptsMask |
+					  INTERRUPT_WAIT_WAKEUP,
+					  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
+					  100,
+					  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 	else if (--absorb_counter <= 0)
 	{
@@ -833,10 +824,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -925,12 +912,11 @@ IsCheckpointOnSchedule(double progress)
  * --------------------------------
  */
 
-/* SIGINT: set flag to trigger writing of shutdown checkpoint */
+/* SIGINT: raise interrupt to trigger writing of shutdown checkpoint */
 static void
 ReqShutdownXLOG(SIGNAL_ARGS)
 {
-	ShutdownXLOGPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_SHUTDOWN_XLOG);
 }
 
 
@@ -1103,14 +1089,15 @@ RequestCheckpoint(int flags)
 	SpinLockRelease(&CheckpointerShmem->ckpt_lck);
 
 	/*
-	 * Set checkpointer's latch to request checkpoint.  It's possible that the
-	 * checkpointer hasn't started yet, so we will retry a few times if
-	 * needed.  (Actually, more than a few times, since on slow or overloaded
-	 * buildfarm machines, it's been observed that the checkpointer can take
-	 * several seconds to start.)  However, if not told to wait for the
-	 * checkpoint to occur, we consider failure to set the latch to be
-	 * nonfatal and merely LOG it.  The checkpointer should see the request
-	 * when it does start, with or without the SetLatch().
+	 * Send INTERRUPT_WAIT_WAKEUP to wake up the checkpointer.  It's possible
+	 * that the checkpointer hasn't started yet, so we will retry a few times
+	 * if needed.  (Actually, more than a few times, since on slow or
+	 * overloaded buildfarm machines, it's been observed that the checkpointer
+	 * can take several seconds to start.)  However, if not told to wait for
+	 * the checkpoint to occur, we consider failure to wake up the
+	 * checkpointer to be nonfatal and merely LOG it.  The checkpointer should
+	 * see the request when it does start, with or without the
+	 * SendInterrupt().
 	 */
 #define MAX_SIGNAL_TRIES 600	/* max wait 60.0 sec */
 	for (ntries = 0;; ntries++)
@@ -1129,7 +1116,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1260,7 +1247,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 	}
 
 	return true;
@@ -1541,5 +1528,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * 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.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending 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;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 36247c8a254..ddf6a5cf34b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -133,11 +133,6 @@ struct arch_files_state
 
 static struct arch_files_state *arch_files = NULL;
 
-/*
- * Flags set by interrupt handlers for later service in the main loop.
- */
-static volatile sig_atomic_t ready_to_stop = false;
-
 /* ----------
  * Local function forward declarations
  * ----------
@@ -149,10 +144,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -226,18 +222,27 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install an interrupt handler for it?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -248,8 +253,8 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	on_shmem_exit(pgarch_die, 0);
 
 	/*
-	 * Advertise our proc number so that backends can use our latch to wake us
-	 * up while we're sleeping.
+	 * Advertise our proc number so that backends can wake us up while we're
+	 * sleeping.
 	 */
 	PgArch->pgprocno = MyProcNumber;
 
@@ -283,13 +288,12 @@ PgArchWakeup(void)
 	int			arch_pgprocno = PgArch->pgprocno;
 
 	/*
-	 * We don't acquire ProcArrayLock here.  It's actually fine because
-	 * procLatch isn't ever freed, so we just can potentially set the wrong
-	 * process' (or no process') latch.  Even in that case the archiver will
-	 * be relaunched shortly and will start archiving.
+	 * We don't acquire ProcArrayLock here, so we may send the interrupt to
+	 * wrong process, but that's harmless.  Even in that case the archiver
+	 * will be relaunched shortly and will start archiving.
 	 */
 	if (arch_pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(arch_pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, arch_pgprocno);
 }
 
 
@@ -298,8 +302,7 @@ static void
 pgarch_waken_stop(SIGNAL_ARGS)
 {
 	/* set flag to do a final cycle and shut down afterwards */
-	ready_to_stop = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_SHUTDOWN_PGARCH);
 }
 
 /*
@@ -310,7 +313,7 @@ pgarch_waken_stop(SIGNAL_ARGS)
 static void
 pgarch_MainLoop(void)
 {
-	bool		time_to_stop;
+	bool		time_to_stop = false;
 
 	/*
 	 * There shouldn't be anything for the archiver to do except to wait for a
@@ -319,13 +322,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -335,7 +340,7 @@ pgarch_MainLoop(void)
 		 * that the postmaster can start a new archiver if needed.  Also exit
 		 * if time unexpectedly goes backward.
 		 */
-		if (ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_TERMINATE))
 		{
 			time_t		curtime = time(NULL);
 
@@ -347,6 +352,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -357,10 +363,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -409,7 +418,7 @@ pgarch_ArchiverCopyLoop(void)
 			 * command, and the second is to avoid conflicts with another
 			 * archiver spawned by a newer postmaster.
 			 */
-			if (ShutdownRequestPending || !PostmasterIsAlive())
+			if (InterruptPending(INTERRUPT_TERMINATE) || !PostmasterIsAlive())
 				return;
 
 			/*
@@ -417,7 +426,7 @@ pgarch_ArchiverCopyLoop(void)
 			 * we'll adopt a new setting for archive_command as soon as
 			 * possible, even if there is a backlog of files to be archived.
 			 */
-			ProcessPgArchInterrupts();
+			CHECK_FOR_INTERRUPTS();
 
 			/* Reset variables that might be set by the callback */
 			arch_module_check_errdetail_string = NULL;
@@ -850,30 +859,13 @@ pgarch_die(int code, Datum arg)
 	PgArch->pgprocno = INVALID_PROC_NUMBER;
 }
 
-/*
- * Interrupt handler for WAL archiver process.
- *
- * This is called in the loops pgarch_MainLoop and pgarch_ArchiverCopyLoop.
- * It checks for barrier events, config update and request for logging of
- * memory contexts, but not shutdown request because how to handle
- * shutdown request is different between those loops.
- */
 static void
-ProcessPgArchInterrupts(void)
+pgarch_ProcessConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ConfigReloadPending)
 	{
 		char	   *archiveLib = pstrdup(XLogArchiveLibrary);
 		bool		archiveLibChanged;
 
-		ConfigReloadPending = false;
 		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 3fac46c402b..6efc5696996 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -539,10 +539,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +557,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +577,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1644,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1681,20 @@ ServerLoop(void)
 								   0 /* postmaster posts no wait_events */ );
 
 		/*
-		 * Latch set by signal handler, or new connection pending on any of
-		 * our sockets? If the latter, fork a child process to deal with it.
+		 * Interrupt raised by signal handler, or new connection pending on
+		 * any of our sockets?  If the latter, fork a child process to deal
+		 * with it.
 		 */
 		for (int i = 0; i < nevents; i++)
 		{
-			if (events[i].events & WL_LATCH_SET)
-				ResetLatch(MyLatch);
+			if (events[i].events & WL_INTERRUPT)
+				ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/*
 			 * The following requests are handled unconditionally, even if we
-			 * didn't see WL_LATCH_SET.  This gives high priority to shutdown
-			 * and reload requests where the latch happens to appear later in
-			 * events[] or will be reported by a later call to
+			 * didn't see WL_INTERRUPT.  This gives high priority to shutdown
+			 * and reload requests where the interrupt event happens to appear
+			 * later in events[] or will be reported by a later call to
 			 * WaitEventSetWait().
 			 */
 			if (pending_pm_shutdown_request)
@@ -1985,7 +1986,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1996,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2073,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2234,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2581,6 +2582,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2630,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2658,14 @@ CleanupBackend(PMChild *bp,
 	}
 
 	/*
-	 * This backend may have been slated to receive SIGUSR1 when some
-	 * background worker started or stopped.  Cancel those notifications, as
-	 * we don't want to signal PIDs that are not PostgreSQL backends.  This
+	 * This backend may have been slated to receive INTERRUPT_WAIT_WAKEUP when
+	 * some background worker started or stopped.  Cancel those notifications,
+	 * as we don't want to signal PIDs that are not PostgreSQL backends.  This
 	 * gets skipped in the (probably very common) case where the backend has
 	 * never requested any such notifications.
 	 */
 	if (bp_bgworker_notify)
-		BackgroundWorkerStopNotifications(bp_pid);
+		BackgroundWorkerStopNotifications(bp_child_slot);
 
 	/*
 	 * If it was an autovacuum worker, wake up the launcher so that it can
@@ -3921,7 +3924,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4296,15 +4299,15 @@ maybe_start_bgworkers(void)
 		{
 			if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 			{
-				int			notify_pid;
+				ProcNumber	notify_proc_number;
 
-				notify_pid = rw->rw_worker.bgw_notify_pid;
+				notify_proc_number = GetNotifyProcNumberForRegisteredWorker(rw);
 
 				ForgetBackgroundWorker(rw);
 
 				/* Report worker is gone now. */
-				if (notify_pid != 0)
-					kill(notify_pid, SIGUSR1);
+				if (notify_proc_number != INVALID_PROC_NUMBER)
+					SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 330c10ddb0b..bb23642b73f 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * 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;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -77,7 +73,7 @@ int			log_startup_progress_interval = 10000;	/* 10 sec */
 
 /* Signal handlers */
 static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
+static void StartupProcShutdownHandler(SIGNAL_ARGS);
 
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
@@ -92,16 +88,12 @@ static void StartupProcExit(int code, Datum arg);
 static void
 StartupProcTriggerHandler(SIGNAL_ARGS)
 {
-	promote_signaled = true;
-	WakeupRecovery();
-}
-
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-StartupProcSigHupHandler(SIGNAL_ARGS)
-{
-	got_SIGHUP = true;
-	WakeupRecovery();
+	/*
+	 * The INTERRUPT_CHECK_PROMOTE flag hints the startup process to check for
+	 * the signal file, while INTERRUPT_WAL_ARRIVED wakes up the process from
+	 * sleep.
+	 */
+	RaiseInterrupt(INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -111,8 +103,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +122,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +150,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +169,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -225,15 +202,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -241,6 +216,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
 	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
 
+	/* Only set up a subset of the standard interrupt handlers */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessStartupProcTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessStartupProcConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
 	/*
 	 * Unblock signals (they were blocked when the postmaster forked us)
 	 */
@@ -268,7 +250,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -278,18 +260,6 @@ PostRestoreCommand(void)
 	in_restore_command = false;
 }
 
-bool
-IsPromoteSignaled(void)
-{
-	return promote_signaled;
-}
-
-void
-ResetPromoteSignaled(void)
-{
-	promote_signaled = false;
-}
-
 /*
  * Set a flag indicating that it's time to log a progress report.
  */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 74d082ea51c..69542eb69df 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -273,16 +273,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -323,7 +321,7 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	whereToSendOutput = DestNone;
 
 	/*
-	 * Set up a reusable WaitEventSet object we'll use to wait for our latch,
+	 * Set up a reusable WaitEventSet object we'll use to wait for interrupts,
 	 * and (except on Windows) our socket.
 	 *
 	 * Unlike all other postmaster child processes, we'll ignore postmaster
@@ -333,9 +331,12 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * (including the postmaster).
 	 */
 	wes = CreateWaitEventSet(NULL, 2);
-	AddWaitEventToSet(wes, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch, NULL);
+	AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD |
+					  INTERRUPT_WAIT_WAKEUP,	/* for log rotation */
+					  NULL);
 #ifndef WIN32
-	AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], NULL, NULL);
+	AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], 0, NULL);
 #endif
 
 	/* main worker loop */
@@ -351,14 +352,13 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 #endif
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * Process any requests or signals received recently.
 		 */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 
 			/*
@@ -1183,7 +1183,7 @@ pipeThread(void *arg)
 				 ftello(csvlogFile) >= Log_RotationSize * (pgoff_t) 1024) ||
 				(jsonlogFile != NULL &&
 				 ftello(jsonlogFile) >= Log_RotationSize * (pgoff_t) 1024))
-				SetLatch(MyLatch);
+				RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 		LeaveCriticalSection(&sysloggerSection);
 	}
@@ -1194,8 +1194,8 @@ pipeThread(void *arg)
 	/* if there's any data left then force it out now */
 	flush_pipe_input(logbuffer, &bytes_in_logbuffer);
 
-	/* set the latch to waken the main thread, which will quit */
-	SetLatch(MyLatch);
+	/* raise the interrupt to waken the main thread, which will quit */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	LeaveCriticalSection(&sysloggerSection);
 	_endthread();
@@ -1586,9 +1586,10 @@ RemoveLogrotateSignalFiles(void)
 }
 
 /* SIGUSR1: set flag to rotate logfile */
+/*  TODO: Use an interrupt flag for this */
 static void
 sigUsr1Handler(SIGNAL_ARGS)
 {
 	rotation_requested = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 09a8301d669..d02813bd082 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -32,17 +32,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -147,7 +147,7 @@ int			wal_summary_keep_time = 10 * HOURS_PER_DAY * MINS_PER_HOUR;
 
 static void WalSummarizerShutdown(int code, Datum arg);
 static XLogRecPtr GetLatestLSN(TimeLineID *tli);
-static void ProcessWalSummarizerInterrupts(void);
+static void ProcessWalSummarizerConfigReloadInterrupt(void);
 static XLogRecPtr SummarizeWAL(TimeLineID tli, XLogRecPtr start_lsn,
 							   bool exact, XLogRecPtr switch_lsn,
 							   XLogRecPtr maximum_lsn);
@@ -241,16 +241,15 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 			(errmsg_internal("WAL summarizer started")));
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -310,10 +309,8 @@ 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) WaitLatch(NULL,
-						 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 10000,
-						 WAIT_EVENT_WAL_SUMMARIZER_ERROR);
+		(void) WaitInterrupt(0, WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10000,
+							 WAIT_EVENT_WAL_SUMMARIZER_ERROR);
 	}
 
 	/* We can now handle ereport(ERROR) */
@@ -350,7 +347,7 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 		MemoryContextReset(context);
 
 		/* Process any signals received recently. */
-		ProcessWalSummarizerInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/* If it's time to remove any old WAL summaries, do that now. */
 		MaybeRemoveOldWalSummaries();
@@ -625,8 +622,8 @@ GetOldestUnsummarizedLSN(TimeLineID *tli, bool *lsn_is_exact)
  *
  * This might not work, because there's no guarantee that the WAL summarizer
  * process was successfully started, and it also might have started but
- * subsequently terminated. So, under normal circumstances, this will get the
- * latch set, but there's no guarantee.
+ * subsequently terminated. So, under normal circumstances, this will send
+ * the interrupt, but there's no guarantee.
  */
 void
 WakeupWalSummarizer(void)
@@ -641,7 +638,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -850,27 +847,17 @@ GetLatestLSN(TimeLineID *tli)
  * Interrupt handler for main loop of WAL summarizer process.
  */
 static void
-ProcessWalSummarizerInterrupts(void)
+ProcessWalSummarizerConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
+	ProcessConfigFile(PGC_SIGHUP);
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending || !summarize_wal)
+	/* If 'summarize_wal' was turned off, exit */
+	if (!summarize_wal && !InterruptPending(INTERRUPT_TERMINATE))
 	{
 		ereport(DEBUG1,
-				errmsg_internal("WAL summarizer shutting down"));
-		proc_exit(0);
+				errmsg_internal("WAL summarizer shutting down because summarize_wal was disabled"));
+		RaiseInterrupt(INTERRUPT_TERMINATE);
 	}
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 /*
@@ -1009,7 +996,7 @@ SummarizeWAL(TimeLineID tli, XLogRecPtr start_lsn, bool exact,
 		XLogRecord *record;
 		uint8		rmid;
 
-		ProcessWalSummarizerInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/* We shouldn't go backward. */
 		Assert(summary_start_lsn <= xlogreader->EndRecPtr);
@@ -1497,7 +1484,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1535,7 +1522,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 				 * current timeline, so more data might show up.  Delay here
 				 * so we don't tight-loop.
 				 */
-				ProcessWalSummarizerInterrupts();
+				CHECK_FOR_INTERRUPTS();
 				summarizer_wait_for_wal();
 
 				/* Recheck end-of-WAL. */
@@ -1635,11 +1622,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 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);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1686,7 +1677,7 @@ MaybeRemoveOldWalSummaries(void)
 		XLogRecPtr	oldest_lsn = InvalidXLogRecPtr;
 		TimeLineID	selected_tli;
 
-		ProcessWalSummarizerInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Pick a timeline for which some summary files still exist on disk,
@@ -1705,7 +1696,7 @@ MaybeRemoveOldWalSummaries(void)
 		{
 			WalSummaryFile *ws = lfirst(lc);
 
-			ProcessWalSummarizerInterrupts();
+			CHECK_FOR_INTERRUPTS();
 
 			/* If it's not on this timeline, it's not time to consider it. */
 			if (selected_tli != ws->tli)
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index f2ba5720986..45dd6600200 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -98,16 +99,14 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -210,12 +209,12 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 
 		/*
 		 * Advertise whether we might hibernate in this cycle.  We do this
-		 * before resetting the latch to ensure that any async commits will
-		 * see the flag set if they might possibly need to wake us up, and
-		 * that we won't miss any signal they send us.  (If we discover work
-		 * to do in the last cycle before we would hibernate, the global flag
-		 * will be set unnecessarily, but little harm is done.)  But avoid
-		 * touching the global flag if it doesn't need to change.
+		 * before clearing the interrupt flag to ensure that any async commits
+		 * will see the flag set if they might possibly need to wake us up,
+		 * and that we won't miss any signal they send us.  (If we discover
+		 * work to do in the last cycle before we would hibernate, the global
+		 * flag will be set unnecessarily, but little harm is done.)  But
+		 * avoid touching the global flag if it doesn't need to change.
 		 */
 		if (hibernating != (left_till_hibernate <= 1))
 		{
@@ -223,11 +222,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -251,9 +249,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 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 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index d78693ffa8e..91024510c78 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,16 +157,17 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
 #include "replication/worker_internal.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -241,12 +242,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
@@ -709,30 +704,6 @@ pa_process_spooled_messages_if_required(void)
 	return true;
 }
 
-/*
- * Interrupt handler for main loop of parallel apply worker.
- */
-static void
-ProcessParallelApplyInterrupts(void)
-{
-	CHECK_FOR_INTERRUPTS();
-
-	if (ShutdownRequestPending)
-	{
-		ereport(LOG,
-				(errmsg("logical replication parallel apply worker for subscription \"%s\" has finished",
-						MySubscription->name)));
-
-		proc_exit(0);
-	}
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-}
-
 /* Parallel apply worker main loop. */
 static void
 LogicalParallelApplyLoop(shm_mq_handle *mqh)
@@ -762,7 +733,18 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 		void	   *data;
 		Size		len;
 
-		ProcessParallelApplyInterrupts();
+		CHECK_FOR_INTERRUPTS();
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER))
+		{
+			ereport(LOG,
+					(errmsg("logical replication parallel apply worker for subscription \"%s\" has finished",
+							MySubscription->name)));
+			proc_exit(0);
+		}
+		if (ConsumeInterrupt(INTERRUPT_TERMINATE))
+			ProcessTerminateInterrupt();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessConfigFile(PGC_SIGHUP);
 
 		/* Ensure we are reading the data into our memory context. */
 		MemoryContextSwitchTo(ApplyMessageContext);
@@ -808,13 +790,17 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 				int			rc;
 
 				/* Wait for more work. */
-				rc = WaitLatch(MyLatch,
-							   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   1000L,
-							   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
+				rc = WaitInterrupt(CheckForInterruptsMask |
+								   INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER |
+								   INTERRUPT_TERMINATE |
+								   INTERRUPT_CONFIG_RELOAD |
+								   INTERRUPT_WAIT_WAKEUP,
+								   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   1000L,
+								   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
 
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				if (rc & WL_INTERRUPT)
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			}
 		}
 		else
@@ -847,9 +833,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_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -874,15 +859,18 @@ ParallelApplyWorkerMain(Datum main_arg)
 	InitializingApplyWorker = true;
 
 	/*
-	 * Setup signal handling.
+	 * Setup interrupt handling.
 	 *
-	 * Note: We intentionally used SIGUSR2 to trigger a graceful shutdown
-	 * initiated by the leader apply worker. This helps to differentiate it
-	 * from the case where we abort the current transaction and exit on
-	 * receiving SIGTERM.
+	 * In addition to the usual interrupts, we use
+	 * INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER as alternative to
+	 * INTERRUPT_TERMINATE to trigger a graceful shutdown initiated by the
+	 * leader apply worker. This helps to differentiate it from the case where
+	 * we abort the current transaction and exit on receiving SIGTERM.
+	 *
+	 * FIXME: It has the same effect, but prints an additional message to the
+	 * log. Is it worth distinguishing, and do we even want the log message?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
+	SetStandardInterruptHandlers();
 	BackgroundWorkerUnblockSignals();
 
 	/*
@@ -943,8 +931,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,29 +965,7 @@ ParallelApplyWorkerMain(Datum main_arg)
 	set_apply_error_context_origin(originname);
 
 	LogicalParallelApplyLoop(mqh);
-
-	/*
-	 * The parallel apply worker must not get here because the parallel apply
-	 * worker will only stop when it receives a SIGTERM or SIGUSR2 from the
-	 * leader, or SIGINT from itself, or when there is an error. None of these
-	 * cases will allow the code to reach here.
-	 */
-	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;
-	SetLatch(MyLatch);
+	Assert(false);				/* LogicalParallelApplyLoop never returns */
 }
 
 /*
@@ -1067,7 +1032,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)
@@ -1077,6 +1042,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1100,8 +1068,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1192,14 +1158,15 @@ pa_send_data(ParallelApplyWorkerInfo *winfo, Size nbytes, const void *data)
 		Assert(result == SHM_MQ_WOULD_BLOCK);
 
 		/* Wait before retrying. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   SHM_SEND_RETRY_INTERVAL_MS,
-					   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   SHM_SEND_RETRY_INTERVAL_MS,
+						   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1264,13 +1231,14 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo,
 			break;
 
 		/* Wait to be signalled. */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 10L,
-						 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L,
+							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 09964198550..bb27f8cfd50 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -59,7 +60,7 @@ LogicalRepWorker *MyLogicalRepWorker = NULL;
 typedef struct LogicalRepCtxStruct
 {
 	/* Supervisor process. */
-	pid_t		launcher_pid;
+	ProcNumber	launcher_procno;
 
 	/* Hash table holding last start times of subscriptions' apply workers. */
 	dsa_handle	last_start_dsa;
@@ -184,7 +185,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -220,27 +220,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -474,6 +475,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->oldest_nonremovable_xid = retain_dead_tuples
 		? MyReplicationSlot->data.xmin
@@ -540,7 +542,6 @@ retry:
 	}
 
 	bgw.bgw_restart_time = BGW_NEVER_RESTART;
-	bgw.bgw_notify_pid = MyProcPid;
 	bgw.bgw_main_arg = Int32GetDatum(slot);
 
 	if (!RegisterDynamicBackgroundWorker(&bgw, &bgw_handle))
@@ -567,7 +568,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -590,13 +591,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -617,7 +619,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 (;;)
@@ -631,13 +633,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -664,7 +667,7 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 	if (worker)
 	{
 		Assert(!isParallelApplyWorker(worker));
-		logicalrep_worker_stop_internal(worker, SIGTERM);
+		logicalrep_worker_stop_internal(worker, INTERRUPT_TERMINATE);
 	}
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -673,8 +676,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -711,13 +715,13 @@ 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, SIGUSR2);
+		logicalrep_worker_stop_internal(worker, INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER);
 
 	LWLockRelease(LogicalRepWorkerLock);
 }
 
 /*
- * Wake up (using latch) any logical replication worker that matches the
+ * Wake up (using interrupt) any logical replication worker that matches the
  * specified worker type, subscription id, and relation id.
  */
 void
@@ -739,7 +743,7 @@ logicalrep_worker_wakeup(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 }
 
 /*
- * Wake up (using latch) the specified logical replication worker.
+ * Wake up (using interrupt) the specified logical replication worker.
  *
  * Caller must hold lock, else worker->proc could change under us.
  */
@@ -748,7 +752,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -816,7 +820,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_TERMINATE);
 		}
 
 		LWLockRelease(LogicalRepWorkerLock);
@@ -848,6 +852,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;
 }
 
@@ -859,7 +864,7 @@ logicalrep_worker_cleanup(LogicalRepWorker *worker)
 static void
 logicalrep_launcher_onexit(int code, Datum arg)
 {
-	LogicalRepCtx->launcher_pid = 0;
+	LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 }
 
 /*
@@ -1020,7 +1025,6 @@ ApplyLauncherRegister(void)
 	snprintf(bgw.bgw_type, BGW_MAXLEN,
 			 "logical replication launcher");
 	bgw.bgw_restart_time = 5;
-	bgw.bgw_notify_pid = 0;
 	bgw.bgw_main_arg = (Datum) 0;
 
 	RegisterBackgroundWorker(&bgw);
@@ -1046,6 +1050,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1194,8 +1199,12 @@ ApplyLauncherWakeupAtCommit(void)
 void
 ApplyLauncherWakeup(void)
 {
-	if (LogicalRepCtx->launcher_pid != 0)
-		kill(LogicalRepCtx->launcher_pid, SIGUSR1);
+	volatile LogicalRepCtxStruct *repctx = LogicalRepCtx;
+	ProcNumber	launcher_procno;
+
+	launcher_procno = repctx->launcher_procno;
+	if (launcher_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE, launcher_procno);
 }
 
 /*
@@ -1209,13 +1218,16 @@ ApplyLauncherMain(Datum main_arg)
 
 	before_shmem_exit(logicalrep_launcher_onexit, (Datum) 0);
 
-	Assert(LogicalRepCtx->launcher_pid == 0);
-	LogicalRepCtx->launcher_pid = MyProcPid;
+	Assert(LogicalRepCtx->launcher_procno == INVALID_PROC_NUMBER);
+	LogicalRepCtx->launcher_procno = MyProcNumber;
 
-	/* Establish signal handlers. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Establish interrupt handlers. */
 	BackgroundWorkerUnblockSignals();
 
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	/*
 	 * Establish connection to nailed catalogs (we only ever access
 	 * pg_subscription).
@@ -1257,6 +1269,7 @@ ApplyLauncherMain(Datum main_arg)
 		 * detection if one of the subscription enables retain_dead_tuples
 		 * option.
 		 */
+		ClearInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE);
 		sublist = get_subscription_list();
 		foreach(lc, sublist)
 		{
@@ -1417,21 +1430,19 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   wait_time,
-					   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_CONFIG_RELOAD |
+						   INTERRUPT_SUBSCRIPTION_CHANGE,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   wait_time,
+						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
 			CHECK_FOR_INTERRUPTS();
-		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+				ProcessConfigFile(PGC_SIGHUP);
 		}
 	}
 
@@ -1586,7 +1597,7 @@ CreateConflictDetectionSlot(void)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 9c92fddd624..67750242f7c 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 91c17928e9c..44b661ee16d 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -63,9 +63,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -84,9 +85,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -109,7 +110,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1228,7 +1229,7 @@ slotsync_reread_config(void)
 	if (is_slotsync_worker)
 		Assert(sync_replication_slots);
 
-	ConfigReloadPending = false;
+	ClearInterrupt(INTERRUPT_CONFIG_RELOAD);
 	ProcessConfigFile(PGC_SIGHUP);
 
 	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
@@ -1291,13 +1292,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1319,7 +1321,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1363,7 +1367,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
@@ -1409,13 +1413,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1423,7 +1430,7 @@ 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 sync_process_pid)
+check_and_set_sync_info(ProcNumber sync_process_procno)
 {
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1435,16 +1442,16 @@ check_and_set_sync_info(pid_t sync_process_pid)
 				errmsg("cannot synchronize replication slots concurrently"));
 	}
 
-	/* The pid must not be already assigned in SlotSyncCtx */
-	Assert(SlotSyncCtx->pid == InvalidPid);
+	/* The procno must not be already assigned in SlotSyncCtx */
+	Assert(SlotSyncCtx->procno == INVALID_PROC_NUMBER);
 
 	SlotSyncCtx->syncing = true;
 
 	/*
-	 * Advertise the required PID so that the startup process can kill the
-	 * slot sync process on promotion.
+	 * Advertise the required proc number so that the startup process can kill
+	 * the slot sync process on promotion.
 	 */
-	SlotSyncCtx->pid = sync_process_pid;
+	SlotSyncCtx->procno = sync_process_procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1459,7 +1466,7 @@ reset_syncing_flag(void)
 {
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 	SlotSyncCtx->syncing = false;
-	SlotSyncCtx->pid = InvalidPid;
+	SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
 	syncing_slots = false;
@@ -1534,19 +1541,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	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);
 
 	/*
@@ -1686,7 +1689,7 @@ update_synced_slots_inactive_since(void)
 		return;
 
 	/* The slot sync worker or the 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);
 
@@ -1725,7 +1728,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1742,7 +1745,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1750,8 +1753,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1759,13 +1762,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1850,7 +1854,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1928,7 +1932,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index f49a4852ecb..30c5640713d 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -112,7 +113,6 @@
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/array.h"
@@ -169,11 +169,12 @@ wait_for_table_state_change(Oid relid, char expected_state)
 		if (!worker)
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return false;
@@ -220,15 +221,16 @@ wait_for_worker_state_change(char expected_state)
 			break;
 
 		/*
-		 * Wait.  We expect to get a latch signal back from the apply worker,
+		 * Wait.  We expect to get an interrupt wakeup from the apply worker,
 		 * but use a timeout in case it dies without sending one.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return false;
@@ -520,7 +522,7 @@ ProcessSyncingTablesForApply(XLogRecPtr current_lsn)
 						 * the existing locks before entering a busy loop.
 						 * This is required to avoid any undetected deadlocks
 						 * due to any existing lock as deadlock detector won't
-						 * be able to detect the waits on the latch.
+						 * be able to detect the waits on the interrupt.
 						 *
 						 * Also close any tables prior to the commit.
 						 */
@@ -699,14 +701,12 @@ copy_read_data(void *outbuf, int minread, int maxread)
 		}
 
 		/*
-		 * Wait for more data or latch.
+		 * Wait for more data or interrupt.
 		 */
-		(void) WaitLatchOrSocket(MyLatch,
-								 WL_SOCKET_READABLE | WL_LATCH_SET |
-								 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
-
-		ResetLatch(MyLatch);
+		(void) WaitInterruptOrSocket(CheckForInterruptsMask,
+									 WL_SOCKET_READABLE | WL_INTERRUPT |
+									 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
 	}
 
 	return bytesread;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 033858752d9..e33da19e952 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -281,7 +282,6 @@
 #include "rewrite/rewriteHandler.h"
 #include "storage/buffile.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/procarray.h"
 #include "tcop/tcopprot.h"
@@ -4057,11 +4057,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();
@@ -4180,11 +4177,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4205,23 +4202,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5948,8 +5944,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index a9092fc2382..7d2aec0923b 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -623,7 +623,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	int			active_pid;
 
 	Assert(name != NULL);
 
@@ -686,7 +685,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -708,7 +706,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 */
@@ -1969,7 +1967,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1979,7 +1977,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		int			active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2063,11 +2060,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2106,22 +2098,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * 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.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2162,7 +2157,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);
@@ -3254,11 +3250,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))
@@ -3268,6 +3261,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 way 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/syncrep.c b/src/backend/replication/syncrep.c
index 9cecc83ed68..7abb2951128 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -266,22 +267,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -295,10 +296,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
-		 * the commit is cleaned up.
+		 * We do NOT clear the interrupt bit, so that the process will die
+		 * after the commit is cleaned up.
 		 */
-		if (ProcDiePending)
+		if (InterruptPending(INTERRUPT_TERMINATE))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -315,9 +316,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.")));
@@ -326,11 +326,15 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		}
 
 		/*
-		 * Wait on latch.  Any condition that should wake us up will set the
-		 * latch, so no need for timeout.
+		 * Wait on interrupt.  Any condition that should wake us up will set
+		 * the appropriate interrupt, so no need for timeout.
 		 */
-		rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1,
-					   WAIT_EVENT_SYNC_REP);
+		rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						   INTERRUPT_TERMINATE |
+						   INTERRUPT_QUERY_CANCEL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH,
+						   -1,
+						   WAIT_EVENT_SYNC_REP);
 
 		/*
 		 * If the postmaster dies, we'll probably never get an acknowledgment,
@@ -338,7 +342,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -945,7 +949,7 @@ SyncRepWakeQueue(bool all, int mode)
 		/*
 		 * Wake only when we have set state and removed from queue.
 		 */
-		SetLatch(&(proc->procLatch));
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 		numprocs++;
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 06d0fc1c23d..6496a2d444f 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -247,16 +248,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
-	/* Properly accept or ignore signals the postmaster might send us */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -440,9 +433,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 				/* Process any requests or signals received recently */
 				CHECK_FOR_INTERRUPTS();
 
-				if (ConfigReloadPending)
+				if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				{
-					ConfigReloadPending = false;
 					ProcessConfigFile(PGC_SIGHUP);
 					/* recompute wakeup times */
 					now = GetCurrentTimestamp();
@@ -515,25 +507,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -681,7 +675,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -713,8 +707,12 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_TERMINATE |
+							 INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
 	if (update_process_title)
@@ -1385,7 +1383,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 45b9d4f09f2..2adab18591f 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -24,6 +24,7 @@
 
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
 #include "storage/pmsignal.h"
@@ -333,7 +334,7 @@ RequestXLogStreaming(TimeLineID tli, XLogRecPtr recptr, const char *conninfo,
 	if (launch)
 		SendPostmasterSignal(PMSIGNAL_START_WALRECEIVER);
 	else if (walrcv_proc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(walrcv_proc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, walrcv_proc);
 }
 
 /*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index f0912f3f670..5c3d4efd771 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * 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
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -202,15 +203,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-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
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -283,7 +281,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,
@@ -371,7 +369,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -739,8 +738,16 @@ HandleUploadManifestPacket(StringInfo buf, off_t *offset,
 {
 	int			mtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
 
-	HOLD_CANCEL_INTERRUPTS();
+	/*
+	 * Like in SocketBackend, don't allow query cancel interrupts while
+	 * reading input from the client, because we might lose sync in the FE/BE
+	 * protocol.
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	pq_startmsgread();
 	mtype = pq_getbyte();
@@ -774,7 +781,9 @@ HandleUploadManifestPacket(StringInfo buf, off_t *offset,
 		ereport(ERROR,
 				(errcode(ERRCODE_CONNECTION_FAILURE),
 				 errmsg("unexpected EOF on client connection with an open transaction")));
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/* Process the message */
 	switch (mtype)
@@ -973,12 +982,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1037,9 +1048,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1476,7 +1487,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1530,7 +1541,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1620,12 +1631,8 @@ WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
  * to changes in synchronous replication requirements.
  */
 static void
-WalSndHandleConfigReload(void)
+ProcessWalSndConfigReloadInterrupt(void)
 {
-	if (!ConfigReloadPending)
-		return;
-
-	ConfigReloadPending = false;
 	ProcessConfigFile(PGC_SIGHUP);
 	SyncRepInitConfig();
 
@@ -1665,23 +1672,27 @@ 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,
+				   CheckForInterruptsMask |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1784,7 +1795,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1871,12 +1882,12 @@ WalSndWaitForWal(XLogRecPtr loc)
 		TimestampTz now;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Check for input from the client */
 		ProcessRepliesIfAny();
@@ -1886,7 +1897,8 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING && !RecoveryInProgress())
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) &&
+			!RecoveryInProgress())
 			XLogFlush(GetXLogInsertRecPtr());
 
 		/*
@@ -1911,7 +1923,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1993,11 +2005,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2023,7 +2039,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2896,6 +2912,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2910,13 +2927,20 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
+
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Check for input from the client */
 		ProcessRepliesIfAny();
@@ -2965,13 +2989,13 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/*
-			 * When SIGUSR2 arrives, we send any outstanding logs up to the
-			 * shutdown checkpoint record (i.e., the latest record), wait for
-			 * them to be replicated to the standby, and exit. This may be a
-			 * normal termination at shutdown, or a promotion, the walsender
-			 * is not sure which.
+			 * When INTERRUPT_WALSND_STOP arrives, we send any outstanding
+			 * logs up to the shutdown checkpoint record (i.e., the latest
+			 * record), wait for them to be replicated to the standby, and
+			 * exit. This may be a normal termination at shutdown, or a
+			 * promotion, the walsender is not sure which.
 			 */
-			if (got_SIGUSR2)
+			if (InterruptPending(INTERRUPT_WALSND_STOP))
 				WalSndDone(send_data);
 		}
 
@@ -3023,7 +3047,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3061,6 +3090,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3117,6 +3147,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);
 }
 
@@ -3211,7 +3242,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3580,8 +3611,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3698,52 +3729,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3826,24 +3848,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;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	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 SetLatch(), 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
@@ -3891,16 +3917,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/aio/aio.c b/src/backend/storage/aio/aio.c
index e4ae3031fef..da2601e8b92 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d9617c20e76..4c21b0f48a3 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,12 +262,12 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
 			pgaio_debug_io(DEBUG4, staged_ios[i],
 						   "choosing worker %d",
@@ -277,8 +276,10 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -392,20 +393,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -453,11 +454,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -490,13 +491,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -567,18 +568,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 5f3d083e938..b9f082a9ba5 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3346,7 +3347,7 @@ WakePinCountWaiter(BufferDesc *buf)
 		UnlockBufHdrExt(buf, buf_state,
 						0, BM_PIN_COUNT_WAITER,
 						0);
-		ProcSendSignal(wait_backend_pgprocno);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wait_backend_pgprocno);
 	}
 	else
 		UnlockBufHdr(buf);
@@ -3526,7 +3527,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3607,7 +3608,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3698,7 +3699,7 @@ BufferSync(int flags)
 		/*
 		 * Sleep to throttle our I/O rate.
 		 *
-		 * (This will check for barrier events even if it doesn't sleep.)
+		 * (This will check for interrupts even if it doesn't sleep.)
 		 */
 		CheckpointWriteDelay(flags, (double) num_processed / num_to_scan);
 	}
@@ -6644,12 +6645,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * 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 signals 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/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e8c07805f59..9715c929f60 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -358,8 +358,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	/*
 	 * Block all blockable signals, except SIGQUIT.  posix_fallocate() can run
 	 * for quite a long time, and is an all-or-nothing operation.  If we
-	 * allowed SIGUSR1 to interrupt us repeatedly (for example, due to
-	 * recovery conflicts), the retry loop might never succeed.
+	 * allowed signals to interrupt us repeatedly (for example, due to config
+	 * file reloads), the retry loop might never succeed.
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..806947571ae 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -172,13 +173,12 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests; we're doing our best to
-	 * close up shop already.  Note that the signal handlers will not set
-	 * these flags again, now that proc_exit_inprogress is set.
+	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
+	 * to prevent any more interrupts from being processed; we're doing our
+	 * best to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 406b8253f8b..3a006e24c50 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -54,6 +54,7 @@
 #include "access/xlogutils.h"
 #include "catalog/catalog.h"
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -3451,27 +3452,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3511,10 +3512,10 @@ SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflict
 				(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 				/*
-				 * Kill the pid if it's still here. If not, that's what we
+				 * Kill the process if it's still here. If not, that's what we
 				 * wanted so ignore any errors.
 				 */
-				(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, vxid.procNumber);
+				SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, vxid.procNumber);
 			}
 			break;
 		}
@@ -3546,21 +3547,18 @@ SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason
 
 		if (databaseid == InvalidOid || proc->databaseId == databaseid)
 		{
-			VirtualTransactionId procvxid;
 			pid_t		pid;
 
-			GET_VXID_FROM_PGPROC(procvxid, *proc);
-
 			pid = proc->pid;
 			if (pid != 0)
 			{
 				(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 				/*
-				 * Kill the pid if it's still here. If not, that's what we
+				 * Kill the process if it's still here. If not, that's what we
 				 * wanted so ignore any errors.
 				 */
-				(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, procvxid.procNumber);
+				SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 			}
 		}
 	}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7e017c8d53b..967db851bf5 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -27,8 +28,8 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -37,28 +38,21 @@
 #include "utils/wait_event.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.
@@ -68,7 +62,6 @@ typedef struct
 	pg_atomic_uint32 pss_pid;
 	int			pss_cancel_key_len; /* 0 means no cancellation is possible */
 	uint8		pss_cancel_key[MAX_CANCEL_KEY_LENGTH];
-	volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
 	slock_t		pss_mutex;		/* protects the above fields */
 
 	/* Barrier-related fields (not protected by pss_mutex) */
@@ -108,7 +101,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);
 
@@ -153,7 +145,6 @@ ProcSignalShmemInit(void)
 			SpinLockInit(&slot->pss_mutex);
 			pg_atomic_init_u32(&slot->pss_pid, 0);
 			slot->pss_cancel_key_len = 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);
@@ -184,9 +175,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* 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
@@ -235,9 +223,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;
@@ -272,73 +261,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
@@ -383,8 +305,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
@@ -395,25 +317,12 @@ 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--)
+	for (ProcNumber pgprocno = 0; pgprocno < NumProcSignalSlots; pgprocno++)
 	{
-		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);
-		}
+		if (pgprocno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_BARRIER);
+		else
+			SendInterrupt(INTERRUPT_BARRIER, pgprocno);
 	}
 
 	return generation;
@@ -473,23 +382,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;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * Perform global barrier related interrupt checking.
  *
@@ -505,12 +397,8 @@ ProcessProcSignalBarrier(void)
 	uint64		shared_gen;
 	volatile uint32 flags;
 
-	Assert(MyProcSignalSlot);
-
-	/* Exit quickly if there's no work to do. */
-	if (!ProcSignalBarrierPending)
+	if (MyProcSignalSlot == NULL)
 		return;
-	ProcSignalBarrierPending = false;
 
 	/*
 	 * It's not unlikely to process multiple barriers at once, before the
@@ -642,68 +530,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))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -762,6 +589,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 26b24158ed9..dd46e5ee97e 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * 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.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -46,10 +47,11 @@
  *
  * mq_detached needs no locking.  It can be set by either the sender or the
  * receiver, but only ever from false to true, so redundant writes don't
- * matter.  It is important that if we set mq_detached and then set the
- * counterparty's latch, the counterparty must be certain to see the change
- * after waking up.  Since SetLatch begins with a memory barrier and ResetLatch
- * ends with one, this should be OK.
+ * matter.  It is important that if we set mq_detached and then send the
+ * 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 SendInterrupt/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.
@@ -114,7 +116,7 @@ struct shm_mq
  * yet updated in the shared memory.  We will not update it until the written
  * 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
- * SetLatch() calls, which are quite expensive.
+ * SendInterrupt() calls, which are quite expensive.
  *
  * mqh_partial_bytes, mqh_expected_bytes, and mqh_length_word_complete
  * are used to track the state of non-blocking operations.  When the caller
@@ -216,7 +218,7 @@ shm_mq_set_receiver(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (sender != NULL)
-		SetLatch(&sender->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(sender));
 }
 
 /*
@@ -234,7 +236,7 @@ shm_mq_set_sender(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (receiver != NULL)
-		SetLatch(&receiver->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(receiver));
 }
 
 /*
@@ -343,16 +345,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 latch when the ring buffer
- * fills up, and then continue writing once the receiver has drained some data.
- * The process latch is reset 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 latch;
- * 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 latch 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_WAIT_WAKEUP
+ * 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
@@ -541,7 +542,7 @@ shm_mq_sendv(shm_mq_handle *mqh, shm_mq_iovec *iov, int iovcnt, bool nowait,
 	{
 		shm_mq_inc_bytes_written(mq, mqh->mqh_send_pending);
 		if (receiver != NULL)
-			SetLatch(&receiver->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(receiver));
 		mqh->mqh_send_pending = 0;
 	}
 
@@ -559,16 +560,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 latch when the ring buffer
- * is empty and we have not yet received a full message.  The sender will
- * set our process latch 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_WAIT_WAKEUP
+ * 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 latch;
- * 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 latch 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_WAIT_WAKEUP has been set.
  */
 shm_mq_result
 shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
@@ -621,8 +621,8 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
 	 * If we've consumed an amount of data greater than 1/4th of the ring
 	 * size, mark it consumed in shared memory.  We try to avoid doing this
 	 * unnecessarily when only a small amount of data has been consumed,
-	 * because SetLatch() is fairly expensive and we don't want to do it too
-	 * often.
+	 * because SendInterrupt() is fairly expensive and we don't want to do it
+	 * too often.
 	 */
 	if (mqh->mqh_consume_pending > mq->mq_ring_size / 4)
 	{
@@ -897,7 +897,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -995,7 +995,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 * Therefore, we can read it without acquiring the spinlock.
 			 */
 			Assert(mqh->mqh_counterparty_attached);
-			SetLatch(&mq->mq_receiver->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(mq->mq_receiver));
 
 			/*
 			 * We have just updated the mqh_send_pending bytes in the shared
@@ -1003,7 +1003,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 */
 			mqh->mqh_send_pending = 0;
 
-			/* Skip manipulation of our latch if nowait = true. */
+			/* Skip waiting if nowait = true. */
 			if (nowait)
 			{
 				*bytes_written = sent;
@@ -1011,17 +1011,18 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			}
 
 			/*
-			 * Wait for our latch to be set.  It might already be set for some
+			 * Wait for the interrupt.  It might already be set for some
 			 * unrelated reason, but that'll just result in one extra trip
-			 * through the loop.  It's worth it to avoid resetting the latch
-			 * at top of loop, because setting an already-set latch is much
-			 * cheaper than setting one that has been reset.
+			 * through the loop.  It's worth it to avoid clearing the
+			 * interrupt at top of loop, because setting an already-set
+			 * interrupt is much cheaper than setting one that has been reset.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_MESSAGE_QUEUE_SEND);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_MESSAGE_QUEUE_SEND);
 
-			/* Reset the latch so we don't spin. */
-			ResetLatch(MyLatch);
+			/* Clear the interrupt so we don't spin. */
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/* An interrupt may have occurred while we were waiting. */
 			CHECK_FOR_INTERRUPTS();
@@ -1056,7 +1057,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 
 			/*
 			 * For efficiency, we don't update the bytes written in the shared
-			 * memory and also don't set the reader's latch here.  Refer to
+			 * memory and also don't send the reader interrupt here.  Refer to
 			 * the comments atop the shm_mq_handle structure for more
 			 * information.
 			 */
@@ -1152,25 +1153,29 @@ shm_mq_receive_bytes(shm_mq_handle *mqh, Size bytes_needed, bool nowait,
 			mqh->mqh_consume_pending = 0;
 		}
 
-		/* Skip manipulation of our latch if nowait = true. */
+		/* Skip waiting if nowait = true. */
 		if (nowait)
 			return SHM_MQ_WOULD_BLOCK;
 
 		/*
-		 * Wait for our latch to be set.  It might already be set for some
+		 * Wait for the interrupt to be set.  It might already be set for some
 		 * unrelated reason, but that'll just result in one extra trip through
-		 * the loop.  It's worth it to avoid resetting the latch at top of
-		 * loop, because setting an already-set latch is much cheaper than
-		 * setting one that has been reset.
+		 * the loop.  It's worth it to avoid clearing the interrupt at top of
+		 * loop, because setting an already-set interrupt is much cheaper than
+		 * setting one that has been cleared.
 		 */
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
-
-		/* An interrupt may have occurred while we were waiting. */
+		/*
+		 * Handle standard interrupts that may have occurred while we were
+		 * waiting.
+		 */
 		CHECK_FOR_INTERRUPTS();
+
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 }
 
@@ -1252,14 +1257,18 @@ shm_mq_wait_internal(shm_mq *mq, PGPROC **ptr, BackgroundWorkerHandle *handle)
 		}
 
 		/* Wait to be signaled. */
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
-
-		/* An interrupt may have occurred while we were waiting. */
+		/*
+		 * Handle standard interrupts that may have occurred while we were
+		 * waiting.
+		 */
 		CHECK_FOR_INTERRUPTS();
+
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return result;
@@ -1295,7 +1304,7 @@ shm_mq_inc_bytes_read(shm_mq *mq, Size n)
 	 */
 	sender = mq->mq_sender;
 	Assert(sender != NULL);
-	SetLatch(&sender->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(sender));
 }
 
 /*
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 800b699de21..b4ed855b20e 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -42,6 +43,9 @@
  * In the event of a general failure (return code 1), a warning message will
  * be emitted. For permission errors, doing that is the responsibility of
  * the caller.
+ *
+ * TODO: This could be changed to send an interrupt directly now. But sending
+ * a SIGTERM or SIGINT still works too.
  */
 #define SIGNAL_BACKEND_SUCCESS 0
 #define SIGNAL_BACKEND_ERROR 1
@@ -202,12 +206,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..dcabd25956a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #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 will set the
- * processes latch. 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
- * setting the process's latch.
- */
-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 */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,15 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * 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
-		 * 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.
+		 * run, which will do the necessary work.  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.
 		 *
 		 * It is awfully tempting to just call AcceptInvalidationMessages()
 		 * without the rest of the xact start/stop overhead, and I think that
@@ -190,14 +148,20 @@ 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();
 		}
+
+		/*
+		 * If another catchup interrupt arrived while we were procesing the
+		 * previous one, process the new one too.
+		 */
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.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
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,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.
  *
@@ -658,8 +666,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)
@@ -670,8 +678,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 f3ad90c7c7a..d1b26f582d3 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -597,7 +598,7 @@ ResolveRecoveryConflictWithDatabase(Oid dbid)
  * ResolveRecoveryConflictWithLock is called from ProcSleep()
  * to resolve conflicts with other backends holding relation locks.
  *
- * The WaitLatch sleep normally done in ProcSleep()
+ * The WaitInterrupt sleep normally done in ProcSleep()
  * (when not InHotStandby) is performed here, for code clarity.
  *
  * We either resolve conflicts immediately or set a timeout to wake us at
@@ -699,7 +700,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 	}
 
 	/* Wait to be signaled by the release of the Relation Lock */
-	ProcWaitForSignal(PG_WAIT_LOCK | locktag.locktag_type);
+	WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+				  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  PG_WAIT_LOCK | locktag.locktag_type);
+	CHECK_FOR_INTERRUPTS();
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/*
 	 * Exit if ltime is reached. Then all the backends holding conflicting
@@ -747,7 +752,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		 * until the relation locks are released or ltime is reached.
 		 */
 		got_standby_deadlock_timeout = false;
-		ProcWaitForSignal(PG_WAIT_LOCK | locktag.locktag_type);
+		WaitInterrupt(CheckForInterruptsMask |
+					  INTERRUPT_WAIT_WAKEUP, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+					  PG_WAIT_LOCK | locktag.locktag_type);
+		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 cleanup:
@@ -767,15 +776,16 @@ cleanup:
  * ResolveRecoveryConflictWithBufferPin is called from LockBufferForCleanup()
  * to resolve conflicts with other backends holding buffer pins.
  *
- * The ProcWaitForSignal() sleep normally done in LockBufferForCleanup()
- * (when not InHotStandby) is performed here, for code clarity.
+ * The WaitInterrupt sleep normally done in LockBufferForCleanup() (when not
+ * InHotStandby) is performed here, for code clarity.
  *
  * 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 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
@@ -839,9 +849,19 @@ 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
-	 * latch from that ProcWaitForSignal() waits on.
+	 * interrupt flag.
+	 *
+	 * FIXME: seems like a shaky assumption. WakeupRecovery() indeed uses a
+	 * different interrupt flag (different latch earlier), but other
+	 * interrupts currently in CheckForInterruptsMask could surely wake us up
+	 * too. However, the caller loops if the buffer is still pinned, so this
+	 * isn't completely broken. The timeouts get reset though.
 	 */
-	ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+	WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+				  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  WAIT_EVENT_BUFFER_CLEANUP);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	CHECK_FOR_INTERRUPTS();
 
 	if (got_standby_delay_timeout)
 		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN);
@@ -937,6 +957,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -946,6 +967,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -955,6 +977,7 @@ void
 StandbyLockTimeoutHandler(void)
 {
 	got_standby_lock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 0f228e1e7b8..81999613313 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -129,13 +130,13 @@ struct WaitEventSet
 	WaitEvent  *events;
 
 	/*
-	 * If WL_LATCH_SET is specified in any wait event, latch is a pointer to
-	 * said latch, and latch_pos the offset in the ->events array. This is
-	 * useful because we check the state of the latch before performing doing
-	 * syscalls related to waiting.
+	 * If WL_INTERRUPT is specified in any wait event, interruptMask is the
+	 * interrupts to wait for, and interrupt_pos the offset in the ->events
+	 * array. This is useful because we check the state of pending interrupts
+	 * before performing syscalls related to waiting.
 	 */
-	Latch	   *latch;
-	int			latch_pos;
+	InterruptMask interrupt_mask;
+	int			interrupt_pos;
 
 	/*
 	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
@@ -168,7 +169,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -184,9 +185,16 @@ static int	selfpipe_writefd = -1;
 
 /* Process owning the self-pipe --- needed for checking purposes */
 static int	selfpipe_owner_pid = 0;
+#endif
+
+#ifdef WAIT_USE_WIN32
+static HANDLE LocalInterruptWakeupEvent;
+#endif
 
 /* Private function prototypes */
-static void latch_sigurg_handler(SIGNAL_ARGS);
+
+#ifdef WAIT_USE_SELF_PIPE
+static void interrupt_sigurg_handler(SIGNAL_ARGS);
 static void sendSelfPipeByte(void);
 #endif
 
@@ -236,7 +244,7 @@ ResourceOwnerForgetWaitEventSet(ResourceOwner owner, WaitEventSet *set)
  * Initialize the process-local wait event infrastructure.
  *
  * This must be called once during startup of any process that can wait on
- * latches, before it issues any InitLatch() or OwnLatch() calls.
+ * interrupts.
  */
 void
 InitializeWaitEventSupport(void)
@@ -286,12 +294,12 @@ InitializeWaitEventSupport(void)
 
 	/*
 	 * Set up the self-pipe that allows a signal handler to wake up the
-	 * poll()/epoll_wait() in WaitLatch. Make the write-end non-blocking, so
-	 * that SetLatch won't block if the event has already been set many times
-	 * filling the kernel buffer. Make the read-end non-blocking too, so that
-	 * we can easily clear the pipe by reading until EAGAIN or EWOULDBLOCK.
-	 * Also, make both FDs close-on-exec, since we surely do not want any
-	 * child processes messing with them.
+	 * poll()/epoll_wait() in WaitInterrupt. Make the write-end non-blocking,
+	 * so that SendInterrupt won't block if the event has already been set
+	 * many times filling the kernel buffer. Make the read-end non-blocking
+	 * too, so that we can easily clear the pipe by reading until EAGAIN or
+	 * EWOULDBLOCK. Also, make both FDs close-on-exec, since we surely do not
+	 * want any child processes messing with them.
 	 */
 	if (pipe(pipefd) < 0)
 		elog(FATAL, "pipe() failed: %m");
@@ -312,7 +320,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -350,6 +358,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -413,7 +427,8 @@ CreateWaitEventSet(ResourceOwner resowner, int nevents)
 	data += MAXALIGN(sizeof(HANDLE) * nevents);
 #endif
 
-	set->latch = NULL;
+	set->interrupt_mask = 0;
+	set->interrupt_pos = -1;
 	set->nevents_space = nevents;
 	set->exit_on_postmaster_death = false;
 
@@ -497,9 +512,9 @@ FreeWaitEventSet(WaitEventSet *set)
 		 cur_event < (set->events + set->nevents);
 		 cur_event++)
 	{
-		if (cur_event->events & WL_LATCH_SET)
+		if (cur_event->events & WL_INTERRUPT)
 		{
-			/* uses the latch's HANDLE */
+			/* uses the process's wakeup HANDLE */
 		}
 		else if (cur_event->events & WL_POSTMASTER_DEATH)
 		{
@@ -536,7 +551,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
 
 /* ---
  * Add an event to the set. Possible events are:
- * - WL_LATCH_SET: Wait for the latch to be set
+ * - WL_INTERRUPT: Wait for the interrupt to be raised
  * - WL_POSTMASTER_DEATH: Wait for postmaster to die
  * - WL_SOCKET_READABLE: Wait for socket to become readable,
  *	 can be combined in one event with other WL_SOCKET_* events
@@ -554,10 +569,6 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * Returns the offset in WaitEventSet->events (starting from 0), which can be
  * used to modify previously added wait events using ModifyWaitEvent().
  *
- * In the WL_LATCH_SET case the latch must be owned by the current process,
- * i.e. it must be a process-local latch initialized with InitLatch, or a
- * shared latch associated with the current process by calling OwnLatch.
- *
  * In the WL_SOCKET_READABLE/WRITEABLE/CONNECTED/ACCEPT cases, EOF and error
  * conditions cause the socket to be reported as readable/writable/connected,
  * so that the caller can deal with the condition.
@@ -567,7 +578,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, InterruptMask interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -581,19 +592,16 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 		set->exit_on_postmaster_death = true;
 	}
 
-	if (latch)
+	/*
+	 * It doesn't make much sense to wait for WL_INTERRUPT with empty
+	 * interruptMask, but we allow it so that you can use ModifyEvent to set
+	 * the interruptMask later. Non-zero interruptMask doesn't make sense
+	 * without WL_INTERRUPT, however.
+	 */
+	if (interruptMask != 0)
 	{
-		if (latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		if (set->latch)
-			elog(ERROR, "cannot wait on more than one latch");
-		if ((events & WL_LATCH_SET) != WL_LATCH_SET)
-			elog(ERROR, "latch events only support being set");
-	}
-	else
-	{
-		if (events & WL_LATCH_SET)
-			elog(ERROR, "cannot wait on latch without a specified latch");
+		if ((events & WL_INTERRUPT) != WL_INTERRUPT)
+			elog(ERROR, "interrupted events only support being set");
 	}
 
 	/* waiting for socket readiness without a socket indicates a bug */
@@ -609,10 +617,10 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 	event->reset = false;
 #endif
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		set->latch = latch;
-		set->latch_pos = event->pos;
+		set->interrupt_mask = interruptMask;
+		set->interrupt_pos = event->pos;
 #if defined(WAIT_USE_SELF_PIPE)
 		event->fd = selfpipe_readfd;
 #elif defined(WAIT_USE_SIGNALFD)
@@ -646,14 +654,14 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 }
 
 /*
- * Change the event mask and, in the WL_LATCH_SET case, the latch associated
- * with the WaitEvent.  The latch may be changed to NULL to disable the latch
- * temporarily, and then set back to a latch later.
+ * Change the event mask and, in the WL_INTERRUPT case, the interrupt mask
+ * associated with the WaitEvent.  The interrupt mask may be changed to 0 to
+ * disable the wakeups on interrupts temporarily, and then set back later.
  *
  * 'pos' is the id returned by AddWaitEventToSet.
  */
 void
-ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
+ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, InterruptMask interruptMask)
 {
 	WaitEvent  *event;
 #if defined(WAIT_USE_KQUEUE)
@@ -683,40 +691,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -746,9 +748,8 @@ WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
 	epoll_ev.events = EPOLLERR | EPOLLHUP;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		epoll_ev.events |= EPOLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -795,9 +796,8 @@ WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
 	pollfd->fd = event->fd;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		pollfd->events = POLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -859,9 +859,9 @@ WaitEventAdjustKqueueAddPostmaster(struct kevent *k_ev, WaitEvent *event)
 }
 
 static inline void
-WaitEventAdjustKqueueAddLatch(struct kevent *k_ev, WaitEvent *event)
+WaitEventAdjustKqueueAddInterruptWakeup(struct kevent *k_ev, WaitEvent *event)
 {
-	/* For now latch can only be added, not removed. */
+	/* For now interrupt wakeup can only be added, not removed. */
 	k_ev->ident = SIGURG;
 	k_ev->filter = EVFILT_SIGNAL;
 	k_ev->flags = EV_ADD;
@@ -887,8 +887,7 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 	if (old_events == event->events)
 		return;
 
-	Assert(event->events != WL_LATCH_SET || set->latch != NULL);
-	Assert(event->events == WL_LATCH_SET ||
+	Assert(event->events == WL_INTERRUPT ||
 		   event->events == WL_POSTMASTER_DEATH ||
 		   (event->events & (WL_SOCKET_READABLE |
 							 WL_SOCKET_WRITEABLE |
@@ -903,10 +902,10 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 		 */
 		WaitEventAdjustKqueueAddPostmaster(&k_ev[count++], event);
 	}
-	else if (event->events == WL_LATCH_SET)
+	else if (event->events == WL_INTERRUPT)
 	{
-		/* We detect latch wakeup using a signal event. */
-		WaitEventAdjustKqueueAddLatch(&k_ev[count++], event);
+		/* We detect interrupt wakeup using a signal event. */
+		WaitEventAdjustKqueueAddInterruptWakeup(&k_ev[count++], event);
 	}
 	else
 	{
@@ -986,10 +985,13 @@ WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event)
 {
 	HANDLE	   *handle = &set->handles[event->pos + 1];
 
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
-		*handle = set->latch->event;
+		/*
+		 * We register the event even if interrupt_mask is zero. We might get
+		 * some spurious wakeups, but that's OK
+		 */
+		*handle = MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
 	{
@@ -1045,6 +1047,7 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	instr_time	start_time;
 	instr_time	cur_time;
 	long		cur_timeout = -1;
+	bool		sleeping_flag_armed = false;
 
 	Assert(nevents > 0);
 
@@ -1066,100 +1069,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		InterruptMask old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
-
-		if (set->latch &&
-			set->latch->maybe_sleeping)
-			set->latch->maybe_sleeping = false;
-
-		if (rc == -1)
-			break;				/* timeout occurred */
-		else
-			returned_events += rc;
-
-		/* If we're not done, update cur_timeout for next iteration */
-		if (returned_events == 0 && timeout >= 0)
+		for (;;)
 		{
-			INSTR_TIME_SET_CURRENT(cur_time);
-			INSTR_TIME_SUBTRACT(cur_time, start_time);
-			cur_timeout = timeout - (long) INSTR_TIME_GET_MILLISEC(cur_time);
-			if (cur_timeout <= 0)
+			int			rc;
+
+			rc = WaitEventSetWaitBlock(set, cur_timeout,
+									   occurred_events, nevents - returned_events);
+
+			if (rc == -1)
+				break;			/* timeout occurred */
+			else
+				returned_events += rc;
+
+			if (returned_events > 0)
 				break;
+
+			/* If we're not done, update cur_timeout for next iteration */
+			if (timeout >= 0)
+			{
+				INSTR_TIME_SET_CURRENT(cur_time);
+				INSTR_TIME_SUBTRACT(cur_time, start_time);
+				cur_timeout = timeout - (long) INSTR_TIME_GET_MILLISEC(cur_time);
+				if (cur_timeout <= 0)
+					break;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1230,16 +1250,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1392,13 +1412,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1514,16 +1534,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1622,6 +1642,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			cur_event->reset = false;
 		}
 
+		/*
+		 * We need to use different event object depending on whether "local"
+		 * or "shared memory" interrupts are in use. There's no easy way to
+		 * adjust all existing WaitEventSet when you switch from local to
+		 * shared or back, so we refresh it on every call.
+		 */
+		if (cur_event->events & WL_INTERRUPT)
+			WaitEventAdjustWin32(set, cur_event);
+
 		/*
 		 * We associate the socket with a new event handle for each
 		 * WaitEventSet.  FD_CLOSE is only generated once if the other end
@@ -1727,19 +1756,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1784,7 +1809,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 
 				/*------
 				 * WaitForMultipleObjects doesn't guarantee that a read event
-				 * will be returned if the latch is set at the same time.  Even
+				 * will be returned if the interrupt is set at the same time.  Even
 				 * if it did, the caller might drop that event expecting it to
 				 * reoccur on next call.  So, we must force the event to be
 				 * reset if this WaitEventSet is used again in order to avoid
@@ -1890,18 +1915,17 @@ GetNumRegisteredWaitEvents(WaitEventSet *set)
 #if defined(WAIT_USE_SELF_PIPE)
 
 /*
- * SetLatch uses SIGURG to wake up the process waiting on the latch.
- *
- * Wake up WaitLatch, if we're waiting.
+ * WakeupOtherProc and WakupMyProc use SIGURG to wake up the process waiting
+ * for an interrupt
  */
 static void
-latch_sigurg_handler(SIGNAL_ARGS)
+interrupt_sigurg_handler(SIGNAL_ARGS)
 {
 	if (waiting)
 		sendSelfPipeByte();
 }
 
-/* Send one byte to the self-pipe, to wake up WaitLatch */
+/* Send one byte to the self-pipe, to wake up WaitInterrupt */
 static void
 sendSelfPipeByte(void)
 {
@@ -1918,7 +1942,7 @@ retry:
 
 		/*
 		 * If the pipe is full, we don't need to retry, the data that's there
-		 * already is enough to wake up WaitLatch.
+		 * already is enough to wake up WaitInterrupt.
 		 */
 		if (errno == EAGAIN || errno == EWOULDBLOCK)
 			return;
@@ -2005,7 +2029,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2015,12 +2038,11 @@ ResOwnerReleaseWaitEventSet(Datum res)
  *
  * NB: this function is called from critical sections and signal handlers so
  * throwing an error is not a good idea.
- *
- * On Windows, Latch uses SetEvent directly and this is not used.
  */
 void
 WakeupMyProc(void)
 {
+#ifndef WIN32
 #if defined(WAIT_USE_SELF_PIPE)
 	if (waiting)
 		sendSelfPipeByte();
@@ -2028,12 +2050,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 548b4f66470..05cc71a8b3d 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1585,7 +1586,11 @@ GetSafeSnapshot(Snapshot origSnapshot)
 				 SxactIsROUnsafe(MySerializableXact)))
 		{
 			LWLockRelease(SerializableXactHashLock);
-			ProcWaitForSignal(WAIT_EVENT_SAFE_SNAPSHOT);
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_SAFE_SNAPSHOT);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
 			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 		}
 		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
@@ -3618,7 +3623,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe)
 			 */
 			if (SxactIsDeferrableWaiting(roXact) &&
 				(SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact)))
-				ProcSendSignal(roXact->pgprocno);
+				SendInterrupt(INTERRUPT_WAIT_WAKEUP, roXact->pgprocno);
 		}
 	}
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 2c241cb0244..51786eaa674 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -215,6 +216,8 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -302,14 +305,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < FIRST_PREPARED_XACT_PROC_NUMBER)
 		{
+#ifdef WIN32
+			SECURITY_ATTRIBUTES sa;
+
+			/*
+			 * Set up security attributes to specify that the events are
+			 * inherited.
+			 */
+			ZeroMemory(&sa, sizeof(sa));
+			sa.nLength = sizeof(sa);
+			sa.bInheritHandle = TRUE;
+
+			proc->interruptWakeupEvent = CreateEvent(&sa, TRUE, FALSE, NULL);
+			if (proc->interruptWakeupEvent == NULL)
+				elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 			proc->sem = PGSemaphoreCreate();
-			InitSharedLatch(&(proc->procLatch));
 			LWLockInitialize(&(proc->fpInfoLock), LWTRANCHE_LOCK_FASTPATH);
 		}
 
@@ -520,13 +537,8 @@ InitProcess(void)
 	MyProc->clogGroupMemberLsn = InvalidXLogRecPtr;
 	Assert(pg_atomic_read_u32(&MyProc->clogGroupNext) == INVALID_PROC_NUMBER);
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -691,13 +703,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -720,6 +727,10 @@ InitAuxiliaryProcess(void)
 		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
 	if (MyBackendType == B_CHECKPOINTER)
 		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+	if (MyBackendType == B_WAL_RECEIVER)
+		pg_atomic_write_u32(&ProcGlobal->walreceiverProc, MyProcNumber);
+	if (MyBackendType == B_STARTUP)
+		pg_atomic_write_u32(&ProcGlobal->startupProc, MyProcNumber);
 
 	/*
 	 * Arrange to clean up at process exit.
@@ -989,21 +1000,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1062,7 +1072,7 @@ AuxiliaryProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/* look at the equivalent ProcKill() code for comments */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	/* If this was one of aux processes advertised in ProcGlobal, clear it */
@@ -1081,11 +1091,20 @@ AuxiliaryProcKill(int code, Datum arg)
 		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
 		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
 	}
+	if (MyBackendType == B_WAL_RECEIVER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walreceiverProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_STARTUP)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->startupProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
+	}
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	SpinLockAcquire(&ProcGlobal->freeProcsLock);
 
@@ -1413,18 +1432,18 @@ ProcSleep(LOCALLOCK *locallock)
 	}
 
 	/*
-	 * If somebody wakes us between LWLockRelease and WaitLatch, the latch
-	 * will not wait. But a set latch does not necessarily mean that the lock
-	 * is free now, as there are many other sources for latch sets than
-	 * somebody releasing the lock.
+	 * If somebody wakes us between LWLockRelease and WaitInterrupt,
+	 * WaitInterrupt will not wait. But an interrupt does not necessarily mean
+	 * that the lock is free now, as there are many other sources for the
+	 * interrupt than somebody releasing the lock.
 	 *
-	 * We process interrupts whenever the latch has been set, so cancel/die
-	 * interrupts are processed quickly. This means we must not mind losing
-	 * control to a cancel/die interrupt here.  We don't, because we have no
-	 * shared-state-change work to do after being granted the lock (the
-	 * grantor did it all).  We do have to worry about canceling the deadlock
-	 * timeout and updating the locallock table, but if we lose control to an
-	 * error, LockErrorCleanup will fix that up.
+	 * We process interrupts whenever the interrupt has been set, so
+	 * cancel/die interrupts are processed quickly. This means we must not
+	 * mind losing control to a cancel/die interrupt here.  We don't, because
+	 * we have no shared-state-change work to do after being granted the lock
+	 * (the grantor did it all).  We do have to worry about canceling the
+	 * deadlock timeout and updating the locallock table, but if we lose
+	 * control to an error, LockErrorCleanup will fix that up.
 	 */
 	do
 	{
@@ -1470,9 +1489,10 @@ ProcSleep(LOCALLOCK *locallock)
 		}
 		else
 		{
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
@@ -1678,8 +1698,8 @@ 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
-	 * be reported as a lock timeout, not a user cancel.
+	 * already raised INTERRUPT_QUERY_CANCEL, we want the cancel to be
+	 * reported as a lock timeout, not a user cancel.
 	 */
 	if (!InHotStandby)
 	{
@@ -1716,7 +1736,7 @@ ProcSleep(LOCALLOCK *locallock)
 
 
 /*
- * ProcWakeup -- wake up a process by setting its latch.
+ * ProcWakeup -- wake up a process by sending it an interrupt.
  *
  *	 Also remove the process from the wait queue and set its waitLink invalid.
  *
@@ -1745,7 +1765,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&proc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1901,14 +1921,12 @@ CheckDeadLockAlert(void)
 	got_deadlock_timeout = true;
 
 	/*
-	 * Have to set the latch again, even if handle_sig_alarm already did. Back
-	 * then got_deadlock_timeout wasn't yet set... It's unlikely that this
-	 * ever would be a problem, but setting a set latch again is cheap.
-	 *
-	 * Note that, when this function runs inside procsignal_sigusr1_handler(),
-	 * the handler function sets the latch again after the latch is set here.
+	 * Have to raise the interrupt again, even if handle_sig_alarm already
+	 * 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.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	errno = save_errno;
 }
 
@@ -1987,34 +2005,6 @@ GetLockHoldersAndWaiters(LOCALLOCK *locallock, StringInfo lock_holders_sbuf,
 	}
 }
 
-/*
- * ProcWaitForSignal - wait for a signal from another backend.
- *
- * As this uses the generic process latch the caller has to be robust against
- * unrelated wakeups: Always check that the desired state has occurred, and
- * wait again if not.
- */
-void
-ProcWaitForSignal(uint32 wait_event_info)
-{
-	(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-					 wait_event_info);
-	ResetLatch(MyLatch);
-	CHECK_FOR_INTERRUPTS();
-}
-
-/*
- * ProcSendSignal - set the latch of a backend identified by ProcNumber
- */
-void
-ProcSendSignal(ProcNumber procNumber)
-{
-	if (procNumber < 0 || procNumber >= ProcGlobal->allProcCount)
-		elog(ERROR, "procNumber out of range");
-
-	SetLatch(&GetPGProcByNumber(procNumber)->procLatch);
-}
-
 /*
  * BecomeLockGroupLeader - designate process as lock group leader
  *
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 5391640d861..2dd80e36fdc 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -994,7 +994,7 @@ smgrfd(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, uint32 *off)
 	 * The caller needs to prevent interrupts from being processed, otherwise
 	 * the FD could be closed prematurely.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	fd = smgrsw[reln->smgr_which].smgr_fd(reln, forknum, blocknum, off);
 
@@ -1073,7 +1073,7 @@ smgr_aio_reopen(PgAioHandle *ioh)
 	 * The caller needs to prevent interrupts from being processed, otherwise
 	 * the FD could be closed again before we get to executing the IO.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	if (sd->smgr.is_temp)
 		procno = pgaio_io_get_owner(ioh);
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index 2c964b6f3d9..44c2f463a4f 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -612,8 +612,8 @@ RegisterSyncRequest(const FileTag *ftag, SyncRequestType type,
 		if (ret || (!ret && !retryOnError))
 			break;
 
-		WaitLatch(NULL, WL_EXIT_ON_PM_DEATH | WL_TIMEOUT, 10,
-				  WAIT_EVENT_REGISTER_SYNC_REQUEST);
+		WaitInterrupt(0, WL_EXIT_ON_PM_DEATH | WL_TIMEOUT, 10,
+					  WAIT_EVENT_REGISTER_SYNC_REQUEST);
 	}
 
 	return ret;
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index c517115927c..9595476c8e0 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -198,6 +198,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
 
+	/*
+	 * FIXME: install INTERRUPT_TERMINATE handler at least? Not really needed
+	 * because no one else can send us interrupts yet, and we don't raise them
+	 * within the backend yet either. But if we changed postmaster to send
+	 * INTERRUPT_TERMINATE directly, for example, we'd need to handle it
+	 * already.
+	 */
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5d66c80567c..5e595e51987 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -335,8 +336,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -353,11 +352,23 @@ SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
+
+	/*
+	 * Don't allow query cancel interrupts while reading input from the
+	 * client, because we might lose sync in the FE/BE protocol.  (Die
+	 * interrupts are OK, because we won't read any further messages from the
+	 * client in that case.)
+	 *
+	 * See similar logic in ProcessRecoveryConflictInterrupt().
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Get message type code from the frontend.
 	 */
-	HOLD_CANCEL_INTERRUPTS();
 	pq_startmsgread();
 	qtype = pq_getbyte();
 
@@ -464,7 +475,9 @@ SocketBackend(StringInfo inBuf)
 	 */
 	if (pq_getmessage(inBuf, maxmsglen))
 		return EOF;				/* suitable message already logged */
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	return qtype;
 }
@@ -488,104 +501,6 @@ ReadCommand(StringInfo inBuf)
 	return result;
 }
 
-/*
- * ProcessClientReadInterrupt() - Process interrupts specific to client reads
- *
- * This is called just before and after low-level reads.
- * 'blocked' is true if no data was available to read and we plan to retry,
- * false if about to read or done reading.
- *
- * Must preserve errno!
- */
-void
-ProcessClientReadInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (DoingCommandRead)
-	{
-		/* Check for general interrupts that arrived before/while reading */
-		CHECK_FOR_INTERRUPTS();
-
-		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
-			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 process latch 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 process latch is set, as we might've undesirably
-		 * cleared it while reading.
-		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
-/*
- * ProcessClientWriteInterrupt() - Process interrupts specific to client writes
- *
- * This is called just before and after low-level writes.
- * 'blocked' is true if no data could be written and we plan to retry,
- * false if about to write or done writing.
- *
- * Must preserve errno!
- */
-void
-ProcessClientWriteInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (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 the process latch 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 process latch is set, as we might've undesirably
-		 * cleared it while writing.
-		 */
-		if (blocked)
-		{
-			/*
-			 * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't
-			 * service ProcDiePending.
-			 */
-			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-			{
-				/*
-				 * We don't want to send the client the error message, as a)
-				 * that would possibly block again, and b) it would likely
-				 * lead to loss of protocol sync because we may have already
-				 * sent a partial protocol message.
-				 */
-				if (whereToSendOutput == DestRemote)
-					whereToSendOutput = DestNone;
-
-				CHECK_FOR_INTERRUPTS();
-			}
-		}
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
 /*
  * Do raw parsing (only).
  *
@@ -2910,7 +2825,7 @@ drop_unnamed_stmt(void)
  * Either some backend has bought the farm, or we've been told to shut down
  * "immediately"; so we need to stop what we're doing and exit.
  */
-void
+static void
 quickdie(SIGNAL_ARGS)
 {
 	sigaddset(&BlockSig, SIGQUIT);	/* prevent nested calls */
@@ -3007,50 +2922,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-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 process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3067,22 +2958,91 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
- * in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	/*
+	 * Check the conflicts one by one, clearing each flag only before
+	 * processing the particular conflict.  This ensures that if multiple
+	 * conflicts are pending, we come back here to process the remaining
+	 * conflicts, if an error is thrown during processing one of them.
+	 */
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
+		 reason++)
+	{
+		if ((pending & (1 << reason)) != 0)
+		{
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
+			ProcessRecoveryConflict(reason);
+		}
+	}
+}
+
+void
+ProcessRecoveryConflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
@@ -3242,14 +3202,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,72 +3240,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
-
-	/*
-	 * 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);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * Check the conflicts one by one, clearing each flag only before
-	 * processing the particular conflict.  This ensures that if multiple
-	 * conflicts are pending, we come back here to process the remaining
-	 * conflicts, if an error is thrown during processing one of them.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * 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.
- *
- * 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.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3394,65 +3313,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		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
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * 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 wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
 
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
+
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
+
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	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)
+
 	{
 		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.
@@ -3505,76 +3414,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * 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", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4238,9 +4137,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4253,13 +4156,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4273,7 +4188,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4282,11 +4204,31 @@ 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(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4454,11 +4396,14 @@ 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;
 
 		/* Not reading from the client anymore. */
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 		DoingCommandRead = false;
 
 		/* Make sure libpq is in a good state */
@@ -4639,7 +4584,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 (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4728,6 +4673,28 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		DoingCommandRead = true;
 
+		/*
+		 * Let cache inval catchup requests to be processed while we're idle.
+		 */
+		EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+
+		/*
+		 * When we're truly idle, i.e. no transaction in progress, we can
+		 * additionally process any async LISTEN/NOTIFY messages and stats
+		 * update requests.
+		 */
+		if (!IsTransactionOrTransactionBlock())
+		{
+			EnableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+			EnableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+		}
+
+		/*
+		 * If any of the newly-enabled interrupts are already pending, process
+		 * them now.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * (3) read a command (loop blocks here)
 		 */
@@ -4757,22 +4724,22 @@ 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
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (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/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 87070235d11..6aa69805891 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,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.
@@ -295,14 +295,7 @@ 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);
-	}
+	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 864032c32bf..39f10c254b2 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -347,16 +347,16 @@ pg_sleep(PG_FUNCTION_ARGS)
 	usecs = (int64) Min(secs, (float8) (PG_INT64_MAX / 2));
 
 	/*
-	 * We sleep using WaitLatch, to ensure that we'll wake up promptly if an
-	 * important signal (such as SIGALRM or SIGINT) arrives.  Because
-	 * WaitLatch's upper limit of delay is INT_MAX milliseconds, and the user
-	 * might ask for more than that, we sleep for at most 10 minutes and then
-	 * loop.
+	 * We sleep using WaitInterrupt, to ensure that we'll wake up promptly if
+	 * an important signal (such as SIGALRM or SIGINT) arrives.  Because
+	 * WaitInterrupt's upper limit of delay is INT_MAX milliseconds, and the
+	 * user might ask for more than that, we sleep for at most 10 minutes and
+	 * then loop.
 	 *
 	 * By computing the intended stop time initially, we avoid accumulation of
 	 * extra delay across multiple sleeps.  This also ensures we won't delay
-	 * less than the specified time when WaitLatch is terminated early by a
-	 * non-query-canceling signal such as SIGHUP.
+	 * less than the specified time when WaitInterrupt is terminated early by
+	 * a non-query-canceling signal such as SIGHUP.
 	 */
 	endtime = GetCurrentTimestamp() + usecs;
 
@@ -375,11 +375,10 @@ pg_sleep(PG_FUNCTION_ARGS)
 		else
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 delay_ms,
-						 WAIT_EVENT_PG_SLEEP);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 delay_ms,
+							 WAIT_EVENT_PG_SLEEP);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 6f8cf29c910..b9d51e0e5f3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1737,10 +1737,10 @@ TimestampDifference(TimestampTz start_time, TimestampTz stop_time,
  * TimestampDifferenceMilliseconds -- convert the difference between two
  * 		timestamps into integer milliseconds
  *
- * This is typically used to calculate a wait timeout for WaitLatch()
+ * This is typically used to calculate a wait timeout for WaitInterrupt()
  * or a related function.  The choice of "long" as the result type
  * is to harmonize with that; furthermore, we clamp the result to at most
- * INT_MAX milliseconds, because that's all that WaitLatch() allows.
+ * INT_MAX milliseconds, because that's all that WaitInterrupt() allows.
  *
  * We expect start_time <= stop_time.  If not, we return zero,
  * since then we're already past the previously determined stop_time.
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 80b78f25267..a36c7c33249 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -537,7 +537,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,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;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 8d898790c3e..53b594ca6bd 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -67,8 +66,6 @@ BackendType MyBackendType;
 /* List of lock files to be removed at proc exit */
 static List *lock_files = NIL;
 
-static Latch LocalLatchData;
-
 /* ----------------------------------------------------------------
  *		ignoring system indexes support stuff
  *
@@ -127,10 +124,9 @@ InitPostmasterChild(void)
 	pqinitmask();
 #endif
 
-	/* Initialize process-local latch support */
+	/* Initialize process-local interrupt support */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
-	InitializeLatchWaitSet();
+	InitializeInterruptWaitSet();
 
 	/*
 	 * If possible, make this process a group leader, so that the postmaster
@@ -144,25 +140,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -202,10 +185,9 @@ InitStandaloneProcess(const char *argv0)
 
 	InitProcessGlobals();
 
-	/* Initialize process-local latch support */
+	/* Initialize process-local interrupt support */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
-	InitializeLatchWaitSet();
+	InitializeInterruptWaitSet();
 
 	/*
 	 * For consistency with InitPostmasterChild, initialize signal mask here.
@@ -226,48 +208,6 @@ InitStandaloneProcess(const char *argv0)
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
 
-void
-SwitchToSharedLatch(void)
-{
-	Assert(MyLatch == &LocalLatchData);
-	Assert(MyProc != NULL);
-
-	MyLatch = &MyProc->procLatch;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	/*
-	 * Set the shared latch as the local one might have been set. This
-	 * shouldn't normally be necessary as code is supposed to check the
-	 * condition before waiting for the latch, but a bit care can't hurt.
-	 */
-	SetLatch(MyLatch);
-}
-
-void
-InitProcessLocalLatch(void)
-{
-	MyLatch = &LocalLatchData;
-	InitLatch(MyLatch);
-}
-
-void
-SwitchBackToLocalLatch(void)
-{
-	Assert(MyLatch != &LocalLatchData);
-	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);
-
-	MyLatch = &LocalLatchData;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	SetLatch(MyLatch);
-}
-
 /*
  * Return a human-readable string representation of a BackendType.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..4f1aa94a661 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -1371,20 +1372,19 @@ ShutdownPostgres(int code, Datum arg)
 static void
 StatementTimeoutHandler(void)
 {
-	int			sig = SIGINT;
-
 	/*
 	 * During authentication the timeout is used to deal with
 	 * authentication_timeout - we want to quit in response to such timeouts.
 	 */
 	if (ClientAuthInProgress)
-		sig = SIGTERM;
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+	else
+		RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, sig);
-#endif
-	kill(MyProcPid, sig);
+	/*
+	 * FIXME: we used to signal the whole process group. Is that important, if
+	 * we're e.g. executing archive_command ?
+	 */
 }
 
 /*
@@ -1393,51 +1393,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * 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 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ 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;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling 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.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index ba0bc8e6d4a..19fbd4b11d0 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -171,10 +169,13 @@ autoprewarm_main(Datum main_arg)
 	bool		final_dump_allowed = true;
 	TimestampTz last_dump_time = 0;
 
-	/* Establish signal handlers; once that's done, unblock signals. */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/*
+	 * We will check for INTERRUPT_TERMINATE explicitly in the main loop, so
+	 * disable the normal interrupt handler.
+	 */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
+	/* Default signal handlers are OK for us; unblock signals. */
 	BackgroundWorkerUnblockSignals();
 
 	/* Create (if necessary) and attach to our shared memory area. */
@@ -223,27 +224,27 @@ autoprewarm_main(Datum main_arg)
 	if (first_time)
 	{
 		apw_load_buffers();
-		final_dump_allowed = !ShutdownRequestPending;
+		final_dump_allowed = !InterruptPending(INTERRUPT_TERMINATE);
 		last_dump_time = GetCurrentTimestamp();
 	}
 
 	/* Periodically dump buffers until terminated. */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
-		/* In case of a SIGHUP, just reload the configuration. */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		/* Check for standard interrupts and config reload */
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (autoprewarm_interval <= 0)
 		{
 			/* We're only dumping at shutdown, so just wait forever. */
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-							 -1L,
-							 PG_WAIT_EXTENSION);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_CONFIG_RELOAD |
+								 INTERRUPT_TERMINATE,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								 -1L,
+								 PG_WAIT_EXTENSION);
 		}
 		else
 		{
@@ -267,14 +268,13 @@ autoprewarm_main(Datum main_arg)
 			}
 
 			/* Sleep until the next dump time. */
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 delay_in_ms,
-							 PG_WAIT_EXTENSION);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_CONFIG_RELOAD |
+								 INTERRUPT_TERMINATE,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 delay_in_ms,
+								 PG_WAIT_EXTENSION);
 		}
-
-		/* Reset the latch, loop. */
-		ResetLatch(MyLatch);
 	}
 
 	/*
@@ -424,7 +424,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 (InterruptPending(INTERRUPT_TERMINATE))
 			break;
 
 		/*
@@ -445,7 +445,7 @@ apw_load_buffers(void)
 	LWLockRelease(&apw_state->lock);
 
 	/* Report our success, if we were able to finish. */
-	if (!ShutdownRequestPending)
+	if (!InterruptPending(INTERRUPT_TERMINATE))
 		ereport(LOG,
 				(errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks",
 						apw_state->prewarmed_blocks, num_elements)));
@@ -505,8 +505,7 @@ autoprewarm_database_main(Datum main_arg)
 	BlockInfoRecord blk;
 	dsm_segment *seg;
 
-	/* Establish signal handlers; once that's done, unblock signals. */
-	pqsignal(SIGTERM, die);
+	/* Default signal handlers are OK for us; unblock signals. */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to correct database and get block information. */
@@ -924,9 +923,6 @@ apw_start_leader_worker(void)
 		return;
 	}
 
-	/* must set notify PID to wait for startup */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
@@ -959,9 +955,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 7e2b822d161..2e87676fc56 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -850,8 +850,8 @@ do_sql_command_end(PGconn *conn, const char *sql, bool consume_input)
 	/*
 	 * If requested, consume whatever data is available from the socket. (Note
 	 * that if all data is available, this allows pgfdw_get_result to call
-	 * PQgetResult without forcing the overhead of WaitLatchOrSocket, which
-	 * would be large compared to the overhead of PQconsumeInput.)
+	 * PQgetResult without forcing the overhead of WaitInterruptOrSocket,
+	 * which would be large compared to the overhead of PQconsumeInput.)
 	 */
 	if (consume_input && !PQconsumeInput(conn))
 		pgfdw_report_error(NULL, conn, sql);
@@ -1504,7 +1504,7 @@ pgfdw_cancel_query_end(PGconn *conn, TimestampTz endtime,
 	/*
 	 * If requested, consume whatever data is available from the socket. (Note
 	 * that if all data is available, this allows pgfdw_get_cleanup_result to
-	 * call PQgetResult without forcing the overhead of WaitLatchOrSocket,
+	 * call PQgetResult without forcing the overhead of WaitInterruptOrSocket,
 	 * which would be large compared to the overhead of PQconsumeInput.)
 	 */
 	if (consume_input && !PQconsumeInput(conn))
@@ -1599,7 +1599,7 @@ pgfdw_exec_cleanup_query_end(PGconn *conn, const char *query,
 	/*
 	 * If requested, consume whatever data is available from the socket. (Note
 	 * that if all data is available, this allows pgfdw_get_cleanup_result to
-	 * call PQgetResult without forcing the overhead of WaitLatchOrSocket,
+	 * call PQgetResult without forcing the overhead of WaitInterruptOrSocket,
 	 * which would be large compared to the overhead of PQconsumeInput.)
 	 */
 	if (consume_input && !PQconsumeInput(conn))
@@ -1707,12 +1707,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 				pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 			/* Sleep until there's something to do */
-			wc = WaitLatchOrSocket(MyLatch,
-								   WL_LATCH_SET | WL_SOCKET_READABLE |
-								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								   PQsocket(conn),
-								   cur_timeout, pgfdw_we_cleanup_result);
-			ResetLatch(MyLatch);
+			wc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_INTERRUPT | WL_SOCKET_READABLE |
+									   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									   PQsocket(conn),
+									   cur_timeout, pgfdw_we_cleanup_result);
 
 			CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 60d90329a65..eb925fda39e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2affba74382..06bc57f10bd 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -63,7 +63,7 @@ typedef struct BackgroundWorker
     char        bgw_function_name[BGW_MAXLEN];
     Datum       bgw_main_arg;
     char        bgw_extra[BGW_EXTRALEN];
-    pid_t       bgw_notify_pid;
+    pid_t       bgw_notify_pid;			/* not used */
 } BackgroundWorker;
 </programlisting>
   </para>
@@ -131,6 +131,21 @@ typedef struct BackgroundWorker
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>BGWORKER_NO_NOTIFY</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BGWORKER_NO_NOTIFY</primary></indexterm> Normally,
+       the backend that registers a dynamic worker will be notified
+       with <literal>INTERRUPT_WAIT_WAKEUP</literal> when the worker's state
+       changes, which allows the caller to wait for the worker to start and
+       shut down.  That can be suppressed by setting this flag.
+       <!-- FIXME: Should we reserve a dedicated INTERRUPT bit for it? And if
+            we do, do we even need the BGWORKER_NO_NOTIFY flag, there's very little
+            overhead in sending a useless interrupt -->
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -204,12 +219,8 @@ typedef struct BackgroundWorker
   </para>
 
   <para>
-   <structfield>bgw_notify_pid</structfield> is the PID of a PostgreSQL
-   backend process to which the postmaster should send <literal>SIGUSR1</literal>
-   when the process is started or exits.  It should be 0 for workers registered
-   at postmaster startup time, or when the backend registering the worker does
-   not wish to wait for the worker to start up.  Otherwise, it should be
-   initialized to <literal>MyProcPid</literal>.
+   <structfield>bgw_notify_pid</structfield> is not used and may be removed
+   in a future release.
   </para>
 
   <para>Once running, the process can connect to a database by calling
@@ -260,7 +271,7 @@ typedef struct BackgroundWorker
    reinitializes the cluster due to a backend failure.  Backends which need
    to suspend execution only temporarily should use an interruptible sleep
    rather than exiting; this can be achieved by calling
-   <function>WaitLatch()</function>. Make sure the
+   <function>WaitInterrupt()</function>. Make sure the
    <literal>WL_POSTMASTER_DEATH</literal> flag is set when calling that function, and
    verify the return code for a prompt exit in the emergency case that
    <command>postgres</command> itself has terminated.
@@ -291,10 +302,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..d568e90c064 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 4f40a61e3d9..26a8180b752 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -135,6 +136,7 @@ setup_dynamic_shared_memory(int64 queue_size, int nworkers,
 
 	/* Set up the header region. */
 	hdr = shm_toc_allocate(toc, sizeof(test_shm_mq_header));
+	hdr->leader_proc_number = MyProcNumber;
 	SpinLockInit(&hdr->mutex);
 	hdr->workers_total = nworkers;
 	hdr->workers_attached = 0;
@@ -224,8 +226,6 @@ setup_background_workers(int nworkers, dsm_segment *seg)
 	sprintf(worker.bgw_function_name, "test_shm_mq_main");
 	snprintf(worker.bgw_type, BGW_MAXLEN, "test_shm_mq");
 	worker.bgw_main_arg = UInt32GetDatum(dsm_segment_handle(seg));
-	/* set bgw_notify_pid, so we can detect if the worker stops */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* Register the workers. */
 	for (i = 0; i < nworkers; ++i)
@@ -266,6 +266,9 @@ wait_for_workers_to_become_ready(worker_state *wstate,
 	{
 		int			workers_ready;
 
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+
 		/* If all the workers are ready, we have succeeded. */
 		SpinLockAcquire(&hdr->mutex);
 		workers_ready = hdr->workers_ready;
@@ -288,11 +291,9 @@ wait_for_workers_to_become_ready(worker_state *wstate,
 			we_bgworker_startup = WaitEventExtensionNew("TestShmMqBgWorkerStartup");
 
 		/* Wait to be signaled. */
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 we_bgworker_startup);
-
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 we_bgworker_startup);
 
 		/* 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 0e55287e510..de420d8a076 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/proc.h"
 #include "utils/wait_event.h"
@@ -172,6 +172,8 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 	{
 		bool		wait = true;
 
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+
 		/*
 		 * 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
@@ -236,13 +238,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index e13c05ae5c7..98ad61fc800 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,9 +19,8 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -54,7 +53,6 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/* Unblock signals.  The standard signal handlers are OK for us. */
 	BackgroundWorkerUnblockSignals();
@@ -118,13 +116,7 @@ test_shm_mq_main(Datum main_arg)
 	SpinLockAcquire(&hdr->mutex);
 	++hdr->workers_ready;
 	SpinLockRelease(&hdr->mutex);
-	registrant = BackendPidGetProc(MyBgworkerEntry->bgw_notify_pid);
-	if (registrant == NULL)
-	{
-		elog(DEBUG1, "registrant backend has exited prematurely");
-		proc_exit(1);
-	}
-	SetLatch(&registrant->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, hdr->leader_proc_number);
 
 	/* Do the work. */
 	copy_messages(inqh, outqh);
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index b89954279c0..86780c04c1f 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -156,11 +154,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -214,26 +211,25 @@ worker_spi_main(Datum main_arg)
 
 		/*
 		 * Background workers mustn't call usleep() or any direct equivalent:
-		 * instead, they may wait on their process latch, which sleeps as
-		 * necessary, but is awakened if postmaster dies.  That way the
-		 * background process goes away immediately in an emergency.
+		 * instead, they may use WaitInterrupt, which sleeps as necessary, but
+		 * is awakened if postmaster dies.  That way the background process
+		 * goes away immediately in an emergency.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 worker_spi_naptime * 1000L,
-						 worker_spi_wait_event_main);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_CONFIG_RELOAD,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 worker_spi_naptime * 1000L,
+							 worker_spi_wait_event_main);
 
 		CHECK_FOR_INTERRUPTS();
 
 		/*
-		 * In case of a SIGHUP, just reload the configuration.
+		 * Reload configuration if requested.  This is not done by
+		 * CHECK_FOR_INTERRUPTS() because we only want it to happen here in
+		 * the main loop.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * Start a transaction on which we can run queries.  Note that each
@@ -367,7 +363,6 @@ _PG_init(void)
 	worker.bgw_restart_time = BGW_NEVER_RESTART;
 	sprintf(worker.bgw_library_name, "worker_spi");
 	sprintf(worker.bgw_function_name, "worker_spi_main");
-	worker.bgw_notify_pid = 0;
 
 	/*
 	 * Now fill in worker-specific data, and do the actual registrations.
@@ -421,8 +416,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3250564d4ff..742fc43e5da 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1328,6 +1328,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1565,7 +1566,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2030,6 +2030,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.53.0.1.gb2826b52eb

