From c6590a9631a7d0efcc0cd6ea1757feb8ab4e1708 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
Date: Wed, 24 Jun 2026 23:21:23 +0300
Subject: [PATCH v13 9/9] 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 in
practice, it was cumbersome 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.  But there are now many interrupt bits available, and you can
wait for any number of them in one WaitInterrupt call.

The interrupt bits replace ProcSignals and many direct uses of Unix
signals.  For example, the backend-private ProcDiePending flag is
replaced with the INTERRUPT_TERMINATE interrupt.  SIGTERM still works,
but the default SIGTERM signal handler now just raises
INTERRUPT_TERMINATE, and the interrupt handler does all the work.

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 at 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.

Fixing 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
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 the ProcSignal
infrastructure. (All that's left in procsignal.c is the
ProcSignalBarrier stuff; that should perhaps be moved / renamed now).

Co-authored-by: Thomas Munro <tmunro@postgresql.org>
Reviewed-by: Andres Freund <andres@anarazel.de>
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
---
 contrib/pg_prewarm/autoprewarm.c              |  63 +-
 contrib/pg_stash_advice/stashpersist.c        |  42 +-
 contrib/postgres_fdw/connection.c             |  22 +-
 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/backend/access/heap/vacuumlazy.c          |  10 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/README.parallel    |   2 +-
 src/backend/access/transam/parallel.c         |  80 +--
 src/backend/access/transam/xlog.c             |  13 +-
 src/backend/access/transam/xlogfuncs.c        |  11 +-
 src/backend/access/transam/xlogrecovery.c     |  98 ++-
 src/backend/access/transam/xlogwait.c         |  38 +-
 src/backend/backup/basebackup_throttle.c      |  17 +-
 src/backend/commands/async.c                  | 153 ++---
 src/backend/commands/copyfromparse.c          |  11 +-
 src/backend/commands/repack_worker.c          |   9 +-
 src/backend/commands/vacuum.c                 |  14 +-
 src/backend/executor/nodeAppend.c             |  21 +-
 src/backend/executor/nodeGather.c             |  10 +-
 src/backend/ipc/Makefile                      |   1 +
 src/backend/ipc/README.md                     | 261 ++++++++
 src/backend/ipc/interrupt.c                   | 618 ++++++++++++++++++
 src/backend/ipc/meson.build                   |   1 +
 src/backend/ipc/signal_handlers.c             | 125 ++--
 src/backend/libpq/auth.c                      |   5 +-
 src/backend/libpq/be-secure-gssapi.c          |  17 +-
 src/backend/libpq/be-secure-openssl.c         |   8 +-
 src/backend/libpq/be-secure.c                 |  62 +-
 src/backend/libpq/pqcomm.c                    |  35 +-
 src/backend/libpq/pqmq.c                      |  26 +-
 src/backend/postmaster/autovacuum.c           | 149 ++---
 src/backend/postmaster/auxprocess.c           |   1 -
 src/backend/postmaster/bgworker.c             | 181 ++---
 src/backend/postmaster/bgwriter.c             |  49 +-
 src/backend/postmaster/checkpointer.c         | 188 +++---
 src/backend/postmaster/datachecksum_state.c   |  85 +--
 src/backend/postmaster/pgarch.c               |  97 ++-
 src/backend/postmaster/postmaster.c           |  55 +-
 src/backend/postmaster/startup.c              | 104 +--
 src/backend/postmaster/syslogger.c            |  35 +-
 src/backend/postmaster/walsummarizer.c        |  77 +--
 src/backend/postmaster/walwriter.c            |  46 +-
 .../replication/logical/applyparallelworker.c | 116 ++--
 src/backend/replication/logical/launcher.c    | 134 ++--
 .../replication/logical/sequencesync.c        |   6 +-
 src/backend/replication/logical/slotsync.c    | 183 +++---
 src/backend/replication/logical/tablesync.c   |  37 +-
 src/backend/replication/logical/worker.c      |  47 +-
 src/backend/replication/slot.c                |  14 +-
 src/backend/replication/syncrep.c             |  33 +-
 src/backend/replication/walreceiver.c         |  54 +-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           | 200 +++---
 src/backend/storage/aio/aio.c                 |   2 +-
 src/backend/storage/aio/aio_io.c              |   2 +-
 src/backend/storage/aio/method_worker.c       |  36 +-
 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                 |  14 +-
 src/backend/storage/ipc/latch.c               | 389 -----------
 src/backend/storage/ipc/meson.build           |   1 -
 src/backend/storage/ipc/procarray.c           |  16 +-
 src/backend/storage/ipc/procsignal.c          | 224 +------
 src/backend/storage/ipc/shm_mq.c              | 126 ++--
 src/backend/storage/ipc/signalfuncs.c         |  13 +-
 src/backend/storage/ipc/sinval.c              |  66 +-
 src/backend/storage/ipc/sinvaladt.c           |  23 +-
 src/backend/storage/ipc/standby.c             |  42 +-
 src/backend/storage/ipc/waiteventset.c        | 487 ++++++++------
 src/backend/storage/lmgr/condition_variable.c |  34 +-
 src/backend/storage/lmgr/predicate.c          |   9 +-
 src/backend/storage/lmgr/proc.c               | 151 ++---
 src/backend/storage/smgr/smgr.c               |   4 +-
 src/backend/storage/sync/sync.c               |   7 +-
 src/backend/tcop/backend_startup.c            |   8 +
 src/backend/tcop/postgres.c                   | 600 ++++++++---------
 src/backend/utils/adt/mcxtfuncs.c             |  11 +-
 src/backend/utils/adt/misc.c                  |  26 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/error/elog.c                |   5 +-
 src/backend/utils/init/globals.c              |  24 +-
 src/backend/utils/init/miscinit.c             |  76 +--
 src/backend/utils/init/postinit.c             |  42 +-
 src/backend/utils/misc/timeout.c              |   8 +-
 src/backend/utils/mmgr/mcxt.c                 |  24 +-
 src/include/Makefile                          |   1 +
 src/include/access/parallel.h                 |   4 -
 src/include/access/xlogrecovery.h             |  18 -
 src/include/commands/async.h                  |   6 -
 src/include/commands/repack.h                 |   6 -
 src/include/ipc/interrupt.h                   | 280 +++++---
 src/include/ipc/signal_handlers.h             |   6 +-
 src/include/ipc/standard_interrupts.h         | 212 ++++++
 src/include/libpq/libpq-be-fe-helpers.h       |  47 +-
 src/include/libpq/libpq.h                     |   2 +-
 src/include/libpq/pqmq.h                      |   2 +-
 src/include/meson.build                       |   1 +
 src/include/miscadmin.h                       |   7 +-
 src/include/postmaster/bgworker.h             |  10 +-
 src/include/postmaster/bgworker_internals.h   |   4 +-
 src/include/postmaster/startup.h              |   4 +-
 src/include/replication/logicalworker.h       |   4 -
 src/include/replication/slotsync.h            |   7 -
 src/include/replication/walreceiver.h         |   1 +
 src/include/replication/walsender.h           |   1 -
 src/include/replication/walsender_private.h   |   2 +
 src/include/replication/worker_internal.h     |   5 +-
 src/include/storage/latch.h                   | 142 ----
 src/include/storage/proc.h                    |  17 +-
 src/include/storage/procsignal.h              |  33 -
 src/include/storage/sinval.h                  |   7 -
 src/include/storage/waiteventset.h            |  32 +-
 src/include/tcop/tcopprot.h                   |   6 -
 src/include/utils/memutils.h                  |   1 -
 src/port/pgsleep.c                            |   8 +-
 src/test/isolation/isolationtester.c          |   2 +-
 .../modules/test_checksums/test_checksums.c   |   6 +-
 src/test/modules/test_shm_mq/setup.c          |  15 +-
 src/test/modules/test_shm_mq/test.c           |  14 +-
 src/test/modules/test_shm_mq/test_shm_mq.h    |   3 +
 src/test/modules/test_shm_mq/worker.c         |  10 +-
 src/test/modules/worker_spi/worker_spi.c      |  45 +-
 src/test/perl/PostgreSQL/Test/Cluster.pm      |   4 +-
 src/test/recovery/t/049_wait_for_lsn.pl       |   2 +-
 src/tools/pgindent/typedefs.list              |   4 +-
 130 files changed, 3658 insertions(+), 3568 deletions(-)
 create mode 100644 src/backend/ipc/README.md
 create mode 100644 src/backend/ipc/interrupt.c
 delete mode 100644 src/backend/storage/ipc/latch.c
 create mode 100644 src/include/ipc/standard_interrupts.h
 delete mode 100644 src/include/storage/latch.h

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index f11745f4755..19fbd4b11d0 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,7 +30,7 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
-#include "ipc/signal_handlers.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "storage/buf_internals.h"
@@ -38,9 +38,7 @@
 #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/pg_stash_advice/stashpersist.c b/contrib/pg_stash_advice/stashpersist.c
index 9e08300c541..76c0de0c943 100644
--- a/contrib/pg_stash_advice/stashpersist.c
+++ b/contrib/pg_stash_advice/stashpersist.c
@@ -14,19 +14,19 @@
 #include <sys/stat.h>
 
 #include "common/hashfn.h"
-#include "ipc/signal_handlers.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_stash_advice.h"
 #include "postmaster/bgworker.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/backend_status.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
+#include "utils/wait_classes.h"
 
 typedef struct pgsa_writer_context
 {
@@ -96,10 +96,11 @@ pg_stash_advice_worker_main(Datum main_arg)
 	uint64		last_change_count;
 	TimestampTz last_write_time = 0;
 
-	/* Establish signal handlers; once that's done, unblock signals. */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Establish interrupt handlers */
+	SetStandardInterruptHandlers();
+	/* we'll check for INTERRUPT_TERMINATE ourselves in the main loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+	/* Default signal handlers are OK for us; unblock signals. */
 	BackgroundWorkerUnblockSignals();
 
 	/* Log a debug message */
@@ -151,22 +152,21 @@ pg_stash_advice_worker_main(Datum main_arg)
 	last_change_count = pg_atomic_read_u64(&pgsa_state->change_count);
 
 	/* Periodically write to disk until terminated. */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		/* In case of a SIGHUP, just reload the configuration. */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (pg_stash_advice_persist_interval <= 0)
 		{
 			/* Only writing 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_TERMINATE |
+								 INTERRUPT_CONFIG_RELOAD,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								 -1L,
+								 PG_WAIT_EXTENSION);
 		}
 		else
 		{
@@ -201,13 +201,15 @@ pg_stash_advice_worker_main(Datum main_arg)
 			}
 
 			/* Sleep until the next write time. */
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 delay_in_ms,
-							 PG_WAIT_EXTENSION);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_TERMINATE |
+								 INTERRUPT_CONFIG_RELOAD,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 delay_in_ms,
+								 PG_WAIT_EXTENSION);
 		}
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 	}
 
 	/* Write one last time before exiting. */
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 77f0c1cf82c..7df87dbdbc9 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,12 @@
 #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"
@@ -868,8 +867,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);
@@ -1624,7 +1623,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))
@@ -1719,7 +1718,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))
@@ -1827,12 +1826,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 00af1ce85ee..e595dd37378 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -44,7 +44,7 @@
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
 #include "statistics/statistics.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -8226,7 +8226,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 6c4a4f94769..2bd147db2a8 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -220,7 +220,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/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 75d8554e16c..d52c3a0e2a1 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -149,7 +149,6 @@
 #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/injection_point.h"
@@ -3192,11 +3191,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/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/parallel.c b/src/backend/access/transam/parallel.c
index afb2f92aec4..2c5680a9730 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -29,6 +29,7 @@
 #include "commands/repack.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"
@@ -39,7 +40,6 @@
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "storage/spin.h"
 #include "tcop/tcopprot.h"
 #include "utils/combocid.h"
@@ -119,9 +119,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;
 
@@ -131,9 +128,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.
@@ -617,7 +611,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.
@@ -772,16 +765,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();
+				}
 			}
 		}
 
@@ -890,15 +888,20 @@ 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);
+		/*
+		 * shm_mq sends INTERRUPT_WAIT_WAKEUP to indicate that the
+		 * counterparty has detached, so wake up on that too
+		 */
+		(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)
@@ -1039,21 +1042,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;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1087,9 +1075,6 @@ ProcessParallelMessageInterrupt(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
 	/* Process messages from parallel query workers */
 	ProcessParallelMessages();
 
@@ -1118,6 +1103,7 @@ ProcessParallelMessages(void)
 {
 	dlist_iter	iter;
 
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1352,7 +1338,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. */
@@ -1388,8 +1377,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));
 
@@ -1405,8 +1393,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
@@ -1645,9 +1632,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a8bbf6284a7..6dfc3cfeadf 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"
@@ -89,7 +90,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"
@@ -2674,7 +2674,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);
 	}
 }
 
@@ -10007,11 +10007,10 @@ 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,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 930d632f773..ad56a0048a3 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -30,7 +30,6 @@
 #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"
@@ -737,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,
-					   100L,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   100L,
+						   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 c0ae4d3f63f..6f2fc9d57f0 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 "storage/subsystems.h"
@@ -411,7 +412,6 @@ XLogRecoveryShmemInit(void *arg)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -485,13 +485,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).
@@ -1594,13 +1587,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);
 }
 
 /*
@@ -1730,8 +1716,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
@@ -1760,8 +1746,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))
 			{
@@ -2921,7 +2907,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;
 
@@ -2998,8 +2984,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)
@@ -3007,11 +2993,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;
 
@@ -3032,10 +3023,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;
 }
@@ -3715,16 +3708,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;
@@ -3990,11 +3983,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;
 				}
 
@@ -4010,11 +4004,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 */
@@ -4446,11 +4437,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (ConsumeInterrupt(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4488,7 +4479,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);
 }
 
 /*
@@ -4692,7 +4686,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 d24855385d1..41ca248a226 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 "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "storage/subsystems.h"
@@ -78,7 +78,7 @@ const ShmemCallbacks WaitLSNShmemCallbacks = {
 };
 
 /*
- * 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[] = {
@@ -268,9 +268,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
@@ -325,14 +325,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);
 }
@@ -392,7 +391,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.
@@ -404,7 +403,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);
@@ -462,8 +461,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
@@ -475,7 +475,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index 25527784217..80a47ec935f 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -17,7 +17,6 @@
 #include "backup/basebackup_sink.h"
 #include "ipc/interrupt.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -147,7 +146,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 +163,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/commands/async.c b/src/backend/commands/async.c
index eee8bc29f38..a801f5d7637 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 "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -290,7 +285,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;
 
@@ -320,7 +316,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
@@ -542,15 +538,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;
 
@@ -567,11 +554,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? */
@@ -1274,10 +1260,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));
@@ -1369,7 +1351,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.
@@ -1395,9 +1377,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();
@@ -1625,7 +1607,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
@@ -1645,7 +1627,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);
@@ -2247,14 +2229,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.
@@ -2267,13 +2249,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;
 
@@ -2308,22 +2290,19 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i = listeners[j].procNo;
-			int32		pid;
 			QueuePosition pos;
 
+			Assert(i != INVALID_PROC_NUMBER);
+
 			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);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2340,7 +2319,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))
@@ -2350,7 +2328,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2370,10 +2347,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++;
 		}
@@ -2381,29 +2355,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);
 	}
 }
 
@@ -2540,38 +2505,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;
-
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * 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,
@@ -2580,12 +2519,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));
 }
 
 
@@ -2695,7 +2635,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
@@ -3057,9 +2997,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 65fd5a0ab4f..0db78eef34a 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -273,9 +273,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)
@@ -307,7 +311,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/repack_worker.c b/src/backend/commands/repack_worker.c
index 04d9ff0d83d..eb64a527768 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -26,7 +26,6 @@
 #include "replication/snapbuild.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/memutils.h"
 
@@ -100,8 +99,7 @@ RepackWorkerMain(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(shared->backend_pid,
-						   shared->backend_proc_number);
+	pq_set_parallel_leader(shared->backend_proc_number);
 
 	/* Connect to the database. LOGIN is not required. */
 	BackgroundWorkerInitializeConnectionByOid(shared->dbid, shared->roleid,
@@ -173,9 +171,8 @@ RepackWorkerShutdown(int code, Datum arg)
 {
 	DecodingWorkerShared *shared = (DecodingWorkerShared *) DatumGetPointer(arg);
 
-	SendProcSignal(shared->backend_pid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   shared->backend_proc_number);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  shared->backend_proc_number);
 
 	dsm_detach(worker_dsm_segment);
 }
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 5cada287d79..56b82dae54f 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -43,7 +43,6 @@
 #include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -2442,9 +2441,6 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending)
-		return;
-
 	if (IsParallelWorker())
 	{
 		/*
@@ -2455,7 +2451,7 @@ vacuum_delay_point(bool is_analyze)
 		parallel_vacuum_update_shared_delay_params();
 	}
 
-	if (!VacuumCostActive && !ConfigReloadPending)
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2464,9 +2460,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();
 
@@ -2559,8 +2555,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 c6ab9d5e19e..20d492740b8 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -64,7 +64,6 @@
 #include "executor/nodeAppend.h"
 #include "ipc/interrupt.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "utils/resowner.h"
 #include "utils/wait_event.h"
@@ -1047,7 +1046,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;
@@ -1071,8 +1070,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,
@@ -1082,8 +1081,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)
@@ -1126,14 +1125,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 e500ae668d0..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -35,9 +35,7 @@
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
 #include "ipc/interrupt.h"
-#include "miscadmin.h"
 #include "optimizer/optimizer.h"
-#include "storage/latch.h"
 #include "utils/wait_event.h"
 
 
@@ -384,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
index 4120909ebbe..7d465bc97db 100644
--- a/src/backend/ipc/Makefile
+++ b/src/backend/ipc/Makefile
@@ -13,6 +13,7 @@ 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..085c1d86dee
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,261 @@
+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
+   interrupts 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..06a96fb1456
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,618 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+
+/* Variables for the holdoff mechanism */
+uint32		InterruptHoldoffCount = 0;
+uint32		CritSectionCount = 0;
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/* Bitmask of currently enabled interrupts */
+InterruptMask EnabledInterruptsMask;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static PendingInterrupts LocalPendingInterrupts;
+PendingInterrupts *MyPendingInterrupts = &LocalPendingInterrupts;
+pg_atomic_uint32 *MyPendingInterruptsFlags = &LocalPendingInterrupts.flags;
+const pg_atomic_uint32 ZeroPendingInterruptsFlags;
+
+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;
+	SetInterruptAttentionMask(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;
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_write_u64(&MyPendingInterrupts->attention_mask, EnabledInterruptsMask);
+#else
+	pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_lo, (uint32) EnabledInterruptsMask);
+	pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_hi, (uint32) (EnabledInterruptsMask >> 32));
+#endif
+
+	/*
+	 * Note: the ATTENTION flag might now be unnecessarily set. We don't try
+	 * to clear it here, the next CHECK_FOR_INTERRUPTS() will take care of it.
+	 */
+}
+
+/*
+ * Reset InterruptHoldoffCount and CritSectionCount to given values.  Used
+ * when recovering from an error.
+ */
+void
+ResetInterruptHoldoffCounts(uint32 new_holdoff_count, uint32 new_crit_section_count)
+{
+	InterruptHoldoffCount = new_holdoff_count;
+	CritSectionCount = new_crit_section_count;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		MyPendingInterruptsFlags = &MyPendingInterrupts->flags;
+	else
+		MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
+}
+
+/*
+ * 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() is true, ProcessInterrupts() is
+ * guaranteed to clear all the enabled interrupts 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)
+{
+	uint64		pending;
+	InterruptMask interruptsToProcess;
+
+	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert((pg_atomic_read_u32(&MyPendingInterrupts->flags) & PI_FLAG_SLEEPING) == 0);
+
+	/*
+	 * Clear the ATTENTION flag first. This ensures that if any interrupts are
+	 * received while we're processing, the flag is set again.
+	 */
+	(void) pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+
+	/*
+	 * Make sure others see the clearing of the flags, before we read the
+	 * pending interrupts.
+	 */
+	pg_memory_barrier();
+
+	/* Check once what interrupts are pending */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->pending_mask);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->pending_mask_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->pending_mask_hi) << 32;
+#endif
+	interruptsToProcess = pending & EnabledInterruptsMask;
+
+	if (interruptsToProcess != 0)
+	{
+		Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+		/* Interrupt handlers are not expected to be re-entrant. */
+		HOLD_INTERRUPTS();
+
+		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]) ();
+			}
+		}
+
+		RESUME_INTERRUPTS();
+	}
+
+	/*
+	 * If we get here, we processed all the interrupts that were pending when
+	 * we started.  If any new interrupts arrived while we were processing,
+	 * they must've set the ATTENTION flag again so we'll get back here on the
+	 * next CHECK_FOR_INTERRUPTS().
+	 */
+}
+
+/*
+ * Update MyPendingInterrupts->attention_mask, setting PI_FLAG_ATTENTION if
+ * any of the interrupts in the new mask are already pending.  This should be
+ * called every time after enabling new bits in EnabledInterruptsMask, so that
+ * the next CHECK_FOR_INTERRUPTS() will react correctly to the newly enabled
+ * interrupts.
+ */
+void
+SetInterruptAttentionMask(InterruptMask mask)
+{
+	/*
+	 * This should not be called while sleeping. No other process sets the
+	 * flag, so when we clear/set 'flags' below, we don't need to worry about
+	 * overwriting it.
+	 */
+	Assert((pg_atomic_read_u32(&MyPendingInterrupts->flags) & PI_FLAG_SLEEPING) == 0);
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_write_u64(&MyPendingInterrupts->attention_mask, mask);
+#else
+	pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_lo, (uint32) mask);
+	pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_hi, (uint32) (mask >> 32));
+#endif
+
+	/*
+	 * Make sure other processes see the updated attention_mask before we read
+	 * the interrupts that are currently pending.
+	 *
+	 * XXX: It might be cheaper to use pg_atomic_write_membarrier_u64 variant
+	 * above, paired with pg_atomic_read_membarrier_u64() here to read the
+	 * pending interrupts instead of the barrier-less InterruptPending().
+	 */
+	pg_memory_barrier();
+
+	if (InterruptPending(mask))
+		pg_atomic_write_u32(&MyPendingInterrupts->flags, PI_FLAG_ATTENTION);
+}
+
+
+/*
+ * Make 'new_ptr' the active interrupt vector, transfering all the pending
+ * interrupt bits from the old MyPendingInterrupts vector to the new one.
+ */
+static void
+SwitchMyPendingInterruptsPtr(PendingInterrupts *new_ptr)
+{
+	PendingInterrupts *old_ptr = MyPendingInterrupts;
+
+	/* should not be called while sleeping */
+	Assert((pg_atomic_read_u32(&MyPendingInterrupts->flags) & PI_FLAG_SLEEPING) == 0);
+
+	if (new_ptr == old_ptr)
+		return;
+
+	MyPendingInterrupts = new_ptr;
+	if (MyPendingInterruptsFlags == &old_ptr->flags)
+		MyPendingInterruptsFlags = &new_ptr->flags;
+
+	/*
+	 * 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 'new_ptr', while
+	 * atomically clearing them from 'old_ptr'.  Other backends may continue
+	 * to set bits in 'old_ptr' 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.
+	 */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	{
+		uint64		old_pending;
+
+		old_pending = pg_atomic_exchange_u64(&old_ptr->pending_mask, 0);
+		pg_atomic_fetch_or_u64(&new_ptr->pending_mask, old_pending);
+	}
+#else
+	{
+		uint32		old_pending_lo;
+		uint32		old_pending_hi;
+
+		old_pending_lo = pg_atomic_exchange_u32(&old_ptr->pending_mask_lo, 0);
+		old_pending_hi = pg_atomic_exchange_u32(&old_ptr->pending_mask_hi, 0);
+		pg_atomic_fetch_or_u32(&new_ptr->pending_mask_lo, old_pending_lo);
+		pg_atomic_fetch_or_u32(&new_ptr->pending_mask_hi, old_pending_hi);
+	}
+#endif
+
+	SetInterruptAttentionMask(EnabledInterruptsMask);
+}
+
+/*
+ * 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)
+{
+	SwitchMyPendingInterruptsPtr(&LocalPendingInterrupts);
+}
+
+/*
+ * 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)
+{
+	SwitchMyPendingInterruptsPtr(&MyProc->pendingInterrupts);
+}
+
+static bool
+SendOrRaiseInterrupt(PendingInterrupts *ptr, InterruptMask interruptMask)
+{
+	uint64		old_pending;
+	uint64		attention_mask;
+	uint32		old_flags;
+	bool		wakeup = false;
+
+	/*
+	 * Do an "unlocked" read first, for a quick exit if all the bits are
+	 * already set.
+	 */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	old_pending = pg_atomic_read_u64(&ptr->pending_mask);
+#else
+	old_pending = (uint64) pg_atomic_read_u32(&ptr->pending_mask_lo);
+	old_pending |= (uint64) pg_atomic_read_u32(&ptr->pending_mask_hi) << 32;
+#endif
+
+	if ((interruptMask & ~old_pending) == 0)
+		return false;			/* no new bits were set */
+
+	/* OR our bits to the target */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	(void) pg_atomic_fetch_or_u64(&ptr->pending_mask, interruptMask);
+#else
+	(void) pg_atomic_fetch_or_u32(&ptr->pending_mask_lo, (uint32) interruptMask);
+	(void) pg_atomic_fetch_or_u32(&ptr->pending_mask_hi, (uint32) (interruptMask >> 32));
+#endif
+
+	/*
+	 * Did we set any bits that the requires the target process's ATTENTION?
+	 */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	attention_mask = pg_atomic_read_u64(&ptr->attention_mask);
+#else
+	attention_mask = (uint64) pg_atomic_read_u32(&ptr->attention_mask_lo);
+	attention_mask |= (uint64) pg_atomic_read_u32(&ptr->attention_mask_hi) << 32;
+#endif
+	if ((attention_mask & interruptMask) != 0)
+	{
+		old_flags = pg_atomic_fetch_or_u32(&ptr->flags, PI_FLAG_ATTENTION);
+
+		/*
+		 * Furthermore, if the process is currently sleeping on these
+		 * interrupts, wake it up.
+		 */
+		if ((old_flags & PI_FLAG_SLEEPING) != 0)
+			wakeup = true;
+	}
+
+	return wakeup;
+}
+
+/*
+ * 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)
+{
+	if (SendOrRaiseInterrupt(MyPendingInterrupts, interruptMask))
+		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;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if (SendOrRaiseInterrupt(&proc->pendingInterrupts, interruptMask))
+		WakeupOtherProc(proc);
+}
+
+/*
+ * Like SendInterrupt, but with a cross-check that the process has the given
+ * PID.
+ *
+ * This acquires ProcArrayLock to ensure atomicity, i.e. that the process
+ * doesn't go away while we're about to send the interrupt, so this cannot be
+ * used from postmaster.
+ *
+ * Most interrupts are harmless to send to wrong process, but with others like
+ * INTERRUPT_TERMINATE, not so much.
+ */
+void
+SendInterruptWithPid(InterruptMask interruptMask, ProcNumber pgprocno, pid_t pid)
+{
+	PGPROC	   *proc;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (proc->pid == pid)
+	{
+
+		/*
+		 * If the process is currently blocked waiting for an interrupt to
+		 * arrive, and the interrupt wasn't already pending, wake it up.
+		 */
+		if (SendOrRaiseInterrupt(&proc->pendingInterrupts, interruptMask))
+			WakeupOtherProc(proc);
+	}
+	LWLockRelease(ProcArrayLock);
+}
+
+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
index af7dae10b60..1f698bd0c68 100644
--- a/src/backend/ipc/meson.build
+++ b/src/backend/ipc/meson.build
@@ -1,5 +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
index 44b39bc9ecf..5139b6ef2e3 100644
--- a/src/backend/ipc/signal_handlers.c
+++ b/src/backend/ipc/signal_handlers.c
@@ -3,6 +3,17 @@
  * 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)
+ *
+ * Most places 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
  *
@@ -18,51 +29,87 @@
 
 #include "ipc/interrupt.h"
 #include "ipc/signal_handlers.h"
-#include "miscadmin.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;
+#include "libpq/pqsignal.h"
 
 /*
- * Simple interrupt handler for main loops of background processes.
+ * 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
-ProcessMainLoopInterrupts(void)
+SetPostmasterChildSignalHandlers(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
+	/*----------
+	 * 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, PG_SIG_IGN);
+	pqsignal(SIGUSR1, PG_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, PG_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, PG_SIG_IGN);
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
+	/* 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, PG_SIG_DFL);	/* system() requires this to be SIG_DFL
+									 * rather than SIG_IGN on some platforms */
 
-	if (ShutdownRequestPending)
-		proc_exit(0);
+	/*
+	 * 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);
 
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
+	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 ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
  */
 void
 SignalHandlerForConfigReload(SIGNAL_ARGS)
 {
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 
 /*
@@ -91,19 +138,23 @@ SignalHandlerForCrashExit(SIGNAL_ARGS)
 }
 
 /*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
+ * Simple signal handler for triggering a long-running 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.
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
  */
 void
 SignalHandlerForShutdownRequest(SIGNAL_ARGS)
 {
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
+	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 d7bd3269d69..28280194711 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1678,8 +1678,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)
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 540ed62a5cc..fd9dd1d37dd 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,13 +15,12 @@
 
 #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 +422,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 +458,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 +496,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 +678,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 7890e6c2de2..8b7872fc845 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -29,11 +29,11 @@
 
 #include "common/hashfn.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"
@@ -952,8 +952,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 86ceea72e64..45854936fbc 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"
@@ -178,6 +177,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)
@@ -185,9 +187,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;
@@ -217,7 +216,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);
@@ -232,23 +232,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
@@ -259,12 +258,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -288,7 +281,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;
@@ -304,6 +298,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)
@@ -311,9 +308,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
@@ -342,7 +336,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);
@@ -353,11 +348,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
@@ -368,12 +362,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 34ec18813b4..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -78,7 +78,6 @@
 #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 c55b70e5636..b6b5e08123a 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -21,15 +21,12 @@
 #include "libpq/pqmq.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
-#include "storage/latch.h"
-#include "storage/procsignal.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);
@@ -79,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;
 }
 
@@ -172,17 +168,21 @@ 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)
-			SendProcSignal(pq_mq_parallel_leader_pid,
-						   PROCSIG_PARALLEL_MESSAGE,
-						   pq_mq_parallel_leader_proc_number);
+		if (pq_mq_parallel_leader_proc_number != INVALID_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);
+		/*
+		 * Wait for the shm_mq receiver to send INTERRUPT_WAIT_WAKEUP to us,
+		 * to indicate that it has drained the queue
+		 */
+		(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/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 33d9a8ddcdf..ed02b2ddd5d 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'
@@ -81,10 +83,9 @@
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
-#include "ipc/signal_handlers.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -93,7 +94,6 @@
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -161,9 +161,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;
@@ -296,6 +293,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
@@ -363,7 +363,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);
@@ -432,21 +432,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, PG_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
@@ -493,7 +495,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();
@@ -535,7 +538,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();
 
 		/*
@@ -594,7 +597,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 */
 	}
@@ -609,41 +612,37 @@ 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 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_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);
-
-		ProcessAutoVacLauncherInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * 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])
 			{
@@ -779,21 +778,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? */
@@ -811,17 +806,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();
 }
 
 /*
@@ -1388,8 +1372,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)
@@ -1401,8 +1385,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1437,22 +1420,21 @@ 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);
-
-	/*
-	 * SIGINT is used to signal canceling the current table's vacuum; SIGTERM
-	 * means abort and exit cleanly, and SIGQUIT means abandon ship.
-	 */
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessageInterrupt);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	SetStandardInterruptHandlers();
+
+	/*
+	 * INTERRUPT_QUERY_CANCEL (SIGINT) is used to cancel the current table's
+	 * vacuum; INTERRUPT_TERMINAT (SIGTERM) means abort and exit cleanly as
+	 * usual.
+	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1583,12 +1565,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
 	{
@@ -2362,9 +2339,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);
 
 			/*
@@ -2522,7 +2498,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();
 		{
@@ -2611,9 +2587,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();
 		}
@@ -2735,7 +2710,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 ae7505b9903..32d23a683ef 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -16,7 +16,6 @@
 #include <signal.h>
 
 #include "access/xlog.h"
-#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 2f51814c93d..53cbd277de3 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -14,8 +14,8 @@
 
 #include "access/parallel.h"
 #include "commands/repack.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/bgworker_internals.h"
@@ -24,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 "storage/subsystems.h"
 #include "tcop/tcopprot.h"
@@ -83,6 +81,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;
 
 /*
@@ -221,8 +221,9 @@ BackgroundWorkerShmemInit(void *arg)
 		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;
 	}
@@ -260,6 +261,27 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+	ProcNumber	result;
+
+	/*
+	 * Be extra paranoid and don't trust the memory contents, since this runs
+	 * in the postmaster.
+	 */
+	result = slot->notify_proc_number;
+	if (!(result == INVALID_PROC_NUMBER ||
+		  (result >= 0 && result < MaxBackends + NUM_AUXILIARY_PROCS)))
+	{
+		elog(LOG, "invalid proc number %d in bgworker slot", result);
+		result = INVALID_PROC_NUMBER;
+	}
+
+	return result;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -341,20 +363,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;
@@ -362,8 +384,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;
 		}
@@ -409,23 +431,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;
@@ -447,7 +452,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.
  */
@@ -492,8 +497,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);
 }
 
 /*
@@ -509,12 +514,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
@@ -527,27 +532,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;
+		}
 	}
 }
 
@@ -579,14 +591,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);
 		}
 	}
 }
@@ -640,11 +652,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;
 		}
 	}
 }
@@ -772,32 +779,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);
-		pqsignal(SIGFPE, FloatExceptionHandler);
+		SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
-		/* XXX Any other handlers needed here? */
+		/* like in regular backends */
+		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 	else
 	{
+		/* no cancellation in backends that don't run queries */
 		pqsignal(SIGINT, PG_SIG_IGN);
-		pqsignal(SIGUSR1, PG_SIG_IGN);
 		pqsignal(SIGFPE, PG_SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, PG_SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR2, PG_SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -1003,15 +1006,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
@@ -1127,6 +1121,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.
@@ -1227,8 +1240,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)
@@ -1248,9 +1262,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)
 		{
@@ -1258,7 +1272,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1272,8 +1286,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)
@@ -1291,9 +1306,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)
 		{
@@ -1301,7 +1316,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 f199d92f57c..c8488d99b43 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -33,7 +33,6 @@
 
 #include "access/xlog.h"
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
@@ -98,16 +97,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, PG_SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -221,9 +215,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 +290,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 +320,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 0be0b9009e8..6b301fe0992 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
@@ -87,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
@@ -173,7 +176,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;
@@ -185,7 +187,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);
@@ -215,22 +217,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, PG_SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, PG_SIG_IGN);	/* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_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.
 	 */
@@ -369,15 +377,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;
 
 		/*
@@ -551,10 +559,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;
 		}
 
@@ -579,7 +587,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);
@@ -595,10 +603,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);
 	}
 
 	/*
@@ -607,7 +618,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -625,7 +636,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);
 	}
 
 	/*
@@ -635,55 +646,38 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		ProcessCheckpointerInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
-		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();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
+	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();
 }
 
 /*
@@ -795,24 +789,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;
 
@@ -827,10 +815,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)
 	{
@@ -842,10 +832,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -934,12 +920,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);
 }
 
 
@@ -1097,14 +1082,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++)
@@ -1123,7 +1109,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1254,7 +1240,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;
@@ -1535,5 +1521,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/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 03717a8b008..6f94d7d6be3 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -186,6 +186,8 @@
  */
 #include "postgres.h"
 
+#include <signal.h>
+
 #include "access/genam.h"
 #include "access/heapam.h"
 #include "access/htup_details.h"
@@ -206,10 +208,8 @@
 #include "storage/bufmgr.h"
 #include "storage/checksum.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/lwlock.h"
-#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/smgr.h"
 #include "storage/subsystems.h"
@@ -368,9 +368,6 @@ typedef struct DataChecksumsWorkerDatabase
 	char	   *dbname;
 } DataChecksumsWorkerDatabase;
 
-/* Flag set by the interrupt handler */
-static volatile sig_atomic_t abort_requested = false;
-
 static uint64 worker_invocation;
 
 /*
@@ -394,7 +391,6 @@ static void FreeDatabaseList(List *dblist);
 static DataChecksumsWorkerResult ProcessDatabase(DataChecksumsWorkerDatabase *db);
 static bool ProcessAllDatabases(void);
 static bool ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrategy strategy);
-static void launcher_cancel_handler(SIGNAL_ARGS);
 static void WaitForAllTransactionsToFinish(void);
 
 const ShmemCallbacks DataChecksumsShmemCallbacks = {
@@ -406,7 +402,7 @@ const ShmemCallbacks DataChecksumsShmemCallbacks = {
 		Assert(MyBackendType == B_DATACHECKSUMSWORKER_LAUNCHER);	\
 		LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);			\
 		if (DataChecksumState->launch_operation != operation) 		\
-			abort_requested = true;									\
+			RaiseInterrupt(INTERRUPT_QUERY_CANCEL);					\
 		LWLockRelease(DataChecksumsWorkerLock);						\
 	} while (0)
 
@@ -416,7 +412,7 @@ const ShmemCallbacks DataChecksumsShmemCallbacks = {
 		LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);			\
 		if (DataChecksumState->worker_invocation != worker_invocation || \
 			DataChecksumState->launch_operation != operation) 		\
-			abort_requested = true;									\
+			RaiseInterrupt(INTERRUPT_QUERY_CANCEL);					\
 		LWLockRelease(DataChecksumsWorkerLock);						\
 	} while (0)
 
@@ -750,7 +746,7 @@ ProcessSingleRelationFork(Relation reln, ForkNumber forkNum, BufferAccessStrateg
 		/* Check if we are asked to abort, the abortion will bubble up. */
 		Assert(operation == ENABLE_DATACHECKSUMS);
 		CHECK_FOR_WORKER_ABORT_REQUEST();
-		if (abort_requested)
+		if (InterruptPending(INTERRUPT_QUERY_CANCEL))
 			return false;
 
 		/* update the block counter */
@@ -967,8 +963,6 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 static void
 launcher_exit(int code, Datum arg)
 {
-	abort_requested = false;
-
 	if (launcher_running)
 	{
 		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
@@ -976,7 +970,7 @@ launcher_exit(int code, Datum arg)
 		{
 			ereport(LOG,
 					errmsg("data checksums launcher exiting while worker is still running, signalling worker"));
-			kill(GetPGProcByNumber(DataChecksumState->worker_procno)->pid, SIGTERM);
+			SendInterrupt(INTERRUPT_QUERY_CANCEL, DataChecksumState->worker_procno);
 		}
 		LWLockRelease(DataChecksumsWorkerLock);
 	}
@@ -994,31 +988,6 @@ launcher_exit(int code, Datum arg)
 	LWLockRelease(DataChecksumsWorkerLock);
 }
 
-/*
- * launcher_cancel_handler
- *
- * Internal routine for reacting to SIGINT and flagging the worker to abort.
- * The worker won't be interrupted immediately but will check for abort flag
- * between each block in a relation.
- */
-static void
-launcher_cancel_handler(SIGNAL_ARGS)
-{
-	int			save_errno = errno;
-
-	abort_requested = true;
-
-	/*
-	 * There is no sleeping in the main loop, the flag will be checked
-	 * periodically in ProcessSingleRelationFork. The worker does however
-	 * sleep when waiting for concurrent transactions to end so we still need
-	 * to set the latch.
-	 */
-	SetLatch(MyLatch);
-
-	errno = save_errno;
-}
-
 /*
  * WaitForAllTransactionsToFinish
  *		Blocks awaiting all current transactions to finish
@@ -1051,11 +1020,11 @@ WaitForAllTransactionsToFinish(void)
 		pgstat_report_activity(STATE_RUNNING, activity);
 
 		/* Retry every 3 seconds */
-		ResetLatch(MyLatch);
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   3000,
-					   WAIT_EVENT_CHECKSUM_ENABLE_STARTCONDITION);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_QUERY_CANCEL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   3000,
+						   WAIT_EVENT_CHECKSUM_ENABLE_STARTCONDITION);
 
 		/*
 		 * If the postmaster died, bail out.  But first print a log message to
@@ -1070,7 +1039,7 @@ WaitForAllTransactionsToFinish(void)
 		CHECK_FOR_INTERRUPTS();
 		CHECK_FOR_LAUNCHER_ABORT_REQUEST();
 
-		if (abort_requested)
+		if (InterruptPending(INTERRUPT_QUERY_CANCEL))
 			break;
 	}
 
@@ -1092,11 +1061,7 @@ DataChecksumsWorkerLauncherMain(Datum arg)
 	ereport(DEBUG1,
 			errmsg("background worker \"datachecksums launcher\" started"));
 
-	pqsignal(SIGTERM, die);
-	pqsignal(SIGINT, launcher_cancel_handler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_SIG_IGN);
-
+	SetStandardInterruptHandlers();
 	BackgroundWorkerUnblockSignals();
 
 	MyBackendType = B_DATACHECKSUMSWORKER_LAUNCHER;
@@ -1169,7 +1134,7 @@ again:
 			 * failure, so restart processing instead.
 			 */
 			CHECK_FOR_LAUNCHER_ABORT_REQUEST();
-			if (abort_requested)
+			if (InterruptPending(INTERRUPT_QUERY_CANCEL))
 				goto done;
 			ereport(ERROR,
 					errcode(ERRCODE_INSUFFICIENT_RESOURCES),
@@ -1315,7 +1280,7 @@ ProcessAllDatabases(void)
 					errmsg("data checksums failed to get enabled in all databases, aborting"),
 					errhint("The server log might have more information on the cause of the error."));
 		}
-		else if (result == DATACHECKSUMSWORKER_ABORTED || abort_requested)
+		else if (result == DATACHECKSUMSWORKER_ABORTED || InterruptPending(INTERRUPT_QUERY_CANCEL))
 		{
 			/* Abort flag set, so exit the whole process */
 			return false;
@@ -1569,9 +1534,7 @@ DataChecksumsWorkerMain(Datum arg)
 
 	operation = ENABLE_DATACHECKSUMS;
 
-	pqsignal(SIGTERM, die);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-
+	SetStandardInterruptHandlers();
 	BackgroundWorkerUnblockSignals();
 
 	MyBackendType = B_DATACHECKSUMSWORKER_WORKER;
@@ -1670,7 +1633,7 @@ DataChecksumsWorkerMain(Datum arg)
 		CHECK_FOR_INTERRUPTS();
 		CHECK_FOR_WORKER_ABORT_REQUEST();
 
-		if (abort_requested)
+		if (InterruptPending(INTERRUPT_QUERY_CANCEL))
 			break;
 
 		/*
@@ -1709,7 +1672,7 @@ DataChecksumsWorkerMain(Datum arg)
 	list_free(RelationList);
 	FreeAccessStrategy(strategy);
 
-	if (aborted || abort_requested)
+	if (aborted || InterruptPending(INTERRUPT_QUERY_CANCEL))
 	{
 		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 		if (DataChecksumState->worker_invocation == worker_invocation)
@@ -1773,16 +1736,16 @@ DataChecksumsWorkerMain(Datum arg)
 		pgstat_report_activity(STATE_RUNNING, activity);
 
 		/* Retry every 3 seconds */
-		ResetLatch(MyLatch);
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 3000,
-						 WAIT_EVENT_CHECKSUM_ENABLE_TEMPTABLE_WAIT);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_QUERY_CANCEL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 3000,
+							 WAIT_EVENT_CHECKSUM_ENABLE_TEMPTABLE_WAIT);
 
 		CHECK_FOR_INTERRUPTS();
 		CHECK_FOR_WORKER_ABORT_REQUEST();
 
-		if (aborted || abort_requested)
+		if (aborted || InterruptPending(INTERRUPT_QUERY_CANCEL))
 		{
 			LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 			if (DataChecksumState->worker_invocation == worker_invocation)
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index f9db35c9285..727ed47a3a3 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,7 +34,6 @@
 #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"
@@ -44,7 +43,6 @@
 #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"
@@ -135,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
  * ----------
@@ -151,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);
+
 
 static void PgArchShmemRequest(void *arg);
 static void PgArchShmemInit(void *arg);
@@ -226,18 +220,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, PG_SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_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.  INTERRUPT_TERMINATE is checked explicitly in
+	 * 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 +251,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 +286,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 +300,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 +311,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 +320,14 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		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 +337,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 +349,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -357,10 +360,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(CheckForInterruptsMask |
+							   INTERRUPT_WAIT_WAKEUP |
+							   ((last_sigterm_time == 0) ? INTERRUPT_TERMINATE : 0) |
+							   INTERRUPT_SHUTDOWN_PGARCH,
+							   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 +415,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 +423,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 +856,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 90c7c4528e8..aa0ff6a4c71 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -544,10 +544,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);
@@ -564,7 +562,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
@@ -585,6 +582,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, PG_SIG_IGN);	/* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1660,14 +1659,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);
 	}
 }
 
@@ -1696,19 +1696,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)
@@ -2003,7 +2004,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2013,7 +2014,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2090,7 +2091,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2251,7 +2252,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2593,6 +2594,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;
@@ -2640,6 +2642,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;
@@ -2667,14 +2670,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
@@ -4327,15 +4330,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 e7db3b3e6e1..3f883a017dd 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,13 +22,14 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
-#include "libpq/pqsignal.h"
 #include "ipc/interrupt.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"
@@ -39,21 +40,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.
@@ -78,7 +72,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);
@@ -93,16 +87,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 */
@@ -112,8 +102,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);
 }
 
 /*
@@ -123,7 +121,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * process to restart the walreceiver.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -150,29 +149,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
@@ -185,14 +168,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -226,15 +201,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, PG_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, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -242,6 +215,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)
 	 */
@@ -269,7 +249,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -279,18 +259,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 64d7511ee5e..6ab7942be0c 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,7 +32,7 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
-#include "ipc/signal_handlers.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -45,7 +45,6 @@
 #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"
@@ -292,16 +291,13 @@ 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, PG_SIG_IGN);
 	pqsignal(SIGTERM, PG_SIG_IGN);
 	pqsignal(SIGQUIT, PG_SIG_IGN);
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, PG_SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -342,7 +338,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
@@ -352,9 +348,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 */
@@ -370,14 +369,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);
 
 			/*
@@ -1202,7 +1200,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);
 	}
@@ -1213,8 +1211,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();
@@ -1605,9 +1603,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 ed080cdac49..cecf5de7e08 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -33,7 +33,6 @@
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
@@ -42,7 +41,6 @@
 #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"
@@ -156,7 +154,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 +239,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, PG_SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_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 +307,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 +345,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 +620,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 +636,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -850,27 +845,17 @@ GetLatestLSN(TimeLineID *tli)
  * Interrupt handler for main loop of WAL summarizer process.
  */
 static void
-ProcessWalSummarizerInterrupts(void)
+ProcessWalSummarizerConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
+	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();
 }
 
 /*
@@ -1016,7 +1001,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);
@@ -1507,7 +1492,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1545,7 +1530,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. */
@@ -1645,11 +1630,11 @@ 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(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						 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;
@@ -1696,7 +1681,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,
@@ -1715,7 +1700,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 25745ed30b3..7e23de34526 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,9 +45,8 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
-#include "libpq/pqsignal.h"
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/walwriter.h"
@@ -98,16 +97,15 @@ 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, PG_SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_SIG_IGN);	/* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can reload config file at any point */
+	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 +208,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))
 		{
@@ -224,10 +222,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		}
 
 		/* 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/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 26eb2bb6bfe..d6181693243 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -158,7 +158,6 @@
 #include "postgres.h"
 
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
@@ -167,10 +166,8 @@
 #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 "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
@@ -705,30 +702,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)
@@ -758,7 +731,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);
@@ -804,13 +788,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);
-
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				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_INTERRUPT)
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 				/*
 				 * Force stats reporting to avoid long delays. There can be
@@ -852,9 +840,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -879,15 +866,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();
 
 	/*
@@ -948,8 +938,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;
@@ -986,9 +975,8 @@ ParallelApplyWorkerMain(Datum main_arg)
 
 	/*
 	 * 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.
+	 * worker will only stop when instructed by the leader, or when there is
+	 * an error. None of these cases will allow the code to reach here.
 	 */
 	Assert(false);
 }
@@ -1150,14 +1138,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();
 		}
 
@@ -1222,13 +1211,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);
-
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L,
+							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
+
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_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 962238b8202..17290208352 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -27,7 +27,6 @@
 #include "funcapi.h"
 #include "ipc/interrupt.h"
 #include "lib/dshash.h"
-#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "replication/logicallauncher.h"
@@ -60,7 +59,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;
@@ -193,13 +192,11 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
 		BgwHandleStatus status;
 		pid_t		pid;
-		int			rc;
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -229,28 +226,15 @@ 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);
-
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
-		}
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L, WAIT_EVENT_BGWORKER_STARTUP);
 	}
 
-	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
-	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
-
 	return result;
 }
 
@@ -483,6 +467,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
@@ -549,7 +534,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))
@@ -576,7 +560,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -594,20 +578,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	 */
 	while (worker->in_use && !worker->proc)
 	{
-		int			rc;
-
 		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);
+		CHECK_FOR_INTERRUPTS();
 
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
+		/* Wait a bit --- we don't expect to have to wait long. */
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L, WAIT_EVENT_BGWORKER_STARTUP);
 
 		/* Recheck worker status. */
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
@@ -626,29 +604,23 @@ 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 (;;)
 	{
-		int			rc;
-
 		/* is it gone? */
 		if (!worker->proc || worker->generation != generation)
 			break;
 
 		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);
+		CHECK_FOR_INTERRUPTS();
 
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
+		/* Wait a bit --- we don't expect to have to wait long. */
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
 	}
@@ -673,7 +645,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);
@@ -682,8 +654,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.
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -720,13 +693,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
@@ -748,7 +721,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.
  */
@@ -757,7 +730,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -825,7 +798,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);
@@ -857,6 +830,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;
 }
 
@@ -868,7 +842,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;
 }
 
 /*
@@ -1032,7 +1006,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);
@@ -1047,6 +1020,7 @@ ApplyLauncherShmemInit(void *arg)
 {
 	int			slot;
 
+	LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 	LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 	LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1194,8 +1168,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 +1187,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 +1238,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)
 		{
@@ -1418,21 +1400,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);
-
-		if (rc & WL_LATCH_SET)
+		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_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
 			CHECK_FOR_INTERRUPTS();
-		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+				ProcessConfigFile(PGC_SIGHUP);
 		}
 	}
 
@@ -1587,7 +1567,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 b722288ae39..4899da8d8c9 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 "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
@@ -525,11 +524,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 20828e42499..5d3f2579f6c 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -64,7 +64,6 @@
 #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 "replication/logical.h"
@@ -74,7 +73,6 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/subsystems.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -87,10 +85,10 @@
 /*
  * 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(). On promotion, the startup
- * process sets 'stopSignaled' and uses this 'pid' to signal the synchronizing
- * process with PROCSIG_SLOTSYNC_MESSAGE and also to wake it up so that the
+ * process sets 'stopSignaled' and uses this 'procno' to send the synchronizing
+ * process the INTERRUPT_CHECK_PROMOTE interrupt to wake it up so that the
  * process can immediately stop its synchronizing work.
  * Setting 'stopSignaled' on the other hand is used to handle the race
  * condition when the postmaster has not noticed the promotion yet and thus may
@@ -113,7 +111,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -153,13 +151,6 @@ static long sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
  */
 static bool syncing_slots = false;
 
-/*
- * Interrupt flag set when PROCSIG_SLOTSYNC_MESSAGE is received, asking the
- * slotsync worker or pg_sync_replication_slots() to stop because
- * standby promotion has been triggered.
- */
-volatile sig_atomic_t SlotSyncShutdownPending = false;
-
 /*
  * Structure to hold information fetched from the primary server about a logical
  * replication slot.
@@ -1270,7 +1261,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;
@@ -1333,52 +1324,42 @@ slotsync_reread_config(void)
 }
 
 /*
- * Handle receipt of an interrupt indicating a slotsync shutdown message.
+ * Handle a INTERRUPT_CHECK_PROMOTE, called from ProcessInterrupts().
  *
- * This is called within the SIGUSR1 handler.  All we do here is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessSlotSyncMessage().
+ * In the the slotsync background worker, log a message and exit cleanly.
  */
-void
-HandleSlotSyncMessageInterrupt(void)
+static void
+CheckStopSignaledInWorker(void)
 {
-	InterruptPending = true;
-	SlotSyncShutdownPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	if (!SlotSyncCtx->stopSignaled)
+		return;
+
+	ereport(LOG,
+			errmsg("replication slot synchronization worker will stop because promotion is triggered"));
+	proc_exit(0);
 }
 
 /*
- * Handle a PROCSIG_SLOTSYNC_MESSAGE signal, called from ProcessInterrupts().
- *
- * If the current process is the slotsync background worker, log a message
- * and exit cleanly.  If it is a backend executing pg_sync_replication_slots(),
- * raise an error, unless the sync has already finished, in which case there
- * is no need to interrupt the caller.
+ * In a backend executing pg_sync_replication_slots(), raise an error, unless
+ * the sync has already finished, in which case there is no need to interrupt
+ * the caller. *
  */
-void
-ProcessSlotSyncMessage(void)
+static void
+CheckStopSignaledInBackend(void)
 {
-	SlotSyncShutdownPending = false;
+	if (!SlotSyncCtx->stopSignaled)
+		return;
 
-	if (AmLogicalSlotSyncWorkerProcess())
-	{
-		ereport(LOG,
-				errmsg("replication slot synchronization worker will stop because promotion is triggered"));
-		proc_exit(0);
-	}
-	else
-	{
-		/*
-		 * If sync has already completed, there is no need to interrupt the
-		 * caller with an error.
-		 */
-		if (!IsSyncingReplicationSlots())
-			return;
+	/*
+	 * If sync has already completed, there is no need to interrupt the caller
+	 * with an error.
+	 */
+	if (!IsSyncingReplicationSlots())
+		return;
 
-		ereport(ERROR,
-				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				errmsg("replication slot synchronization will stop because promotion is triggered"));
-	}
+	ereport(ERROR,
+			errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+			errmsg("replication slot synchronization will stop because promotion is triggered"));
 }
 
 /*
@@ -1421,7 +1402,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
@@ -1446,10 +1427,8 @@ slotsync_worker_onexit(int code, Datum arg)
  * sync-cycles is reset to the minimum (200ms).
  */
 static void
-wait_for_slot_activity(bool some_slot_updated)
+wait_for_slot_activity(bool some_slot_updated, InterruptMask interruptMask)
 {
-	int			rc;
-
 	if (!some_slot_updated)
 	{
 		/*
@@ -1467,13 +1446,10 @@ 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);
-
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	(void) WaitInterrupt(interruptMask,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_ms,
+						 WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 }
 
 /*
@@ -1481,7 +1457,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);
 
@@ -1521,16 +1497,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);
 
@@ -1545,7 +1521,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;
@@ -1627,19 +1603,17 @@ 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, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
+	SetStandardInterruptHandlers();
 
-	check_and_set_sync_info(MyProcPid);
+	check_and_set_sync_info(MyProcNumber);
+
+	SetInterruptHandler(INTERRUPT_CHECK_PROMOTE, CheckStopSignaledInWorker);
+	EnableInterrupt(INTERRUPT_CHECK_PROMOTE);
 
 	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);
 
 	/*
@@ -1729,7 +1703,7 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
+		if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 			slotsync_reread_config();
 
 		/*
@@ -1749,7 +1723,8 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 		if (started_tx)
 			CommitTransactionCommand();
 
-		wait_for_slot_activity(some_slot_updated);
+		wait_for_slot_activity(some_slot_updated,
+							   CheckForInterruptsMask | INTERRUPT_CONFIG_RELOAD);
 	}
 
 	/*
@@ -1782,7 +1757,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);
 
@@ -1821,7 +1796,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1838,44 +1813,37 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
 	/*
 	 * Signal process doing slotsync, if any, asking it to stop.
 	 */
-	if (sync_process_pid != InvalidPid)
-		SendProcSignal(sync_process_pid, PROCSIG_SLOTSYNC_MESSAGE,
-					   INVALID_PROC_NUMBER);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_CHECK_PROMOTE, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
 	{
-		int			rc;
+		bool		is_syncing;
 
 		/* 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);
+		WaitInterrupt(CheckForInterruptsMask,
+					  WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					  10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
+		CHECK_FOR_INTERRUPTS();
 
+		/* Ensure that no process is syncing the slots. */
 		SpinLockAcquire(&SlotSyncCtx->mutex);
+		is_syncing = SlotSyncCtx->syncing;
+		SpinLockRelease(&SlotSyncCtx->mutex);
 
-		/* Ensure that no process is syncing the slots. */
-		if (!SlotSyncCtx->syncing)
+		if (!is_syncing)
 			break;
-
-		SpinLockRelease(&SlotSyncCtx->mutex);
 	}
 
-	SpinLockRelease(&SlotSyncCtx->mutex);
-
 	update_synced_slots_inactive_since();
 }
 
@@ -1941,7 +1909,7 @@ static void
 SlotSyncShmemInit(void *arg)
 {
 	memset(SlotSyncCtx, 0, sizeof(SlotSyncCtxStruct));
-	SlotSyncCtx->pid = InvalidPid;
+	SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 	SpinLockInit(&SlotSyncCtx->mutex);
 }
 
@@ -1953,6 +1921,8 @@ slotsync_failure_callback(int code, Datum arg)
 {
 	WalReceiverConn *wrconn = (WalReceiverConn *) DatumGetPointer(arg);
 
+	DisableInterrupt(INTERRUPT_CHECK_PROMOTE);
+
 	/*
 	 * We need to do slots cleanup here just like WalSndErrorCleanup() does.
 	 *
@@ -2019,7 +1989,10 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *slot_names = NIL;	/* List of slot names to track */
 		MemoryContext sync_retry_ctx;
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
+
+		SetInterruptHandler(INTERRUPT_CHECK_PROMOTE, CheckStopSignaledInBackend);
+		EnableInterrupt(INTERRUPT_CHECK_PROMOTE);
 
 		validate_remote_info(wrconn);
 
@@ -2042,7 +2015,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 			/* Check for interrupts and config changes */
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
+			if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 				slotsync_reread_config();
 
 			/* We must be in a valid transaction state */
@@ -2085,7 +2058,9 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 				break;
 
 			/* wait before retrying again */
-			wait_for_slot_activity(some_slot_updated);
+			wait_for_slot_activity(some_slot_updated,
+								   CheckForInterruptsMask |
+								   INTERRUPT_CONFIG_RELOAD);
 		}
 
 		MemoryContextDelete(sync_retry_ctx);
@@ -2093,6 +2068,8 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		if (slot_names)
 			list_free_deep(slot_names);
 
+		DisableInterrupt(INTERRUPT_CHECK_PROMOTE);
+
 		/* Cleanup the synced temporary slots */
 		ReplicationSlotCleanup(true);
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index d6311266fe5..afd2cec06ff 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -112,7 +112,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 +168,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 +220,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 +521,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 +700,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 7219b80c0d4..100e1599874 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -263,7 +263,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
-#include "ipc/signal_handlers.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
@@ -284,7 +284,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"
@@ -4046,6 +4045,7 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 		bool		endofstream = false;
 		long		wait_time;
 
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 
 		MemoryContextSwitchTo(ApplyMessageContext);
@@ -4075,11 +4075,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();
@@ -4198,11 +4195,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 wakeup 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;
@@ -4223,23 +4220,16 @@ 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);
-
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
+		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 (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5967,8 +5957,9 @@ 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();
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 8c61583fbfd..388ad2cb862 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -45,7 +45,6 @@
 #include "common/file_utils.h"
 #include "common/string.h"
 #include "ipc/interrupt.h"
-#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
@@ -2119,7 +2118,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 */
 			if (last_signaled_pid != active_pid)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   active_pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
@@ -2129,7 +2129,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterruptWithPid(INTERRUPT_TERMINATE, active_proc, active_pid);
 
 				last_signaled_pid = active_pid;
 			}
@@ -2172,7 +2172,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   active_pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3270,11 +3271,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))
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 3f1da4dbe4e..cbb7a20b504 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -266,18 +266,18 @@ 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
+		 * Acquiring the lock is not needed, the interrupt 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
@@ -295,10 +295,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))
 		{
 			if (ProcDieSenderPid != 0)
 				ereport(WARNING,
@@ -324,9 +324,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.")));
@@ -335,11 +334,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,
@@ -347,7 +350,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -954,7 +957,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 9abc9817362..b0cf032de3a 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -61,7 +61,6 @@
 #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 "pgstat.h"
@@ -245,16 +244,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, PG_SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, PG_SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -449,9 +440,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();
@@ -524,25 +514,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->apply_reply_requested)
@@ -690,7 +682,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -722,8 +714,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)
@@ -1403,7 +1399,7 @@ WalRcvRequestApplyReply(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 ecf510517eb..bef3187aa26 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"
@@ -342,7 +343,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 b1ad492ae35..9b4cdb2c2c5 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 SIGUSR2, raising 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.
  * This waiting time can be limited by the wal_sender_shutdown_timeout
@@ -65,6 +65,7 @@
 #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"
@@ -88,7 +89,6 @@
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/subsystems.h"
 #include "tcop/dest.h"
 #include "tcop/tcopprot.h"
@@ -229,15 +229,15 @@ 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;
+/* Flag set by interrupt handler, if INIT_STOPPING has been received */
+static bool 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;
 
@@ -312,7 +312,7 @@ static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static void WalSndCheckShutdownTimeout(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, InterruptMask 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,
@@ -400,7 +400,7 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (got_STOPPING || InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -771,8 +771,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();
@@ -806,7 +814,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)
@@ -1070,9 +1080,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. FIXME: can we do better now?
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1533,7 +1543,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1677,12 +1687,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();
 
@@ -1729,23 +1735,26 @@ 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 */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1935,12 +1944,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();
@@ -2070,11 +2079,14 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   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;
 }
 
@@ -2997,7 +3009,7 @@ WalSndCheckShutdownTimeout(void)
 	TimestampTz now;
 
 	/* Do nothing if shutdown has not been requested yet */
-	if (!(got_STOPPING || got_SIGUSR2))
+	if (!(got_STOPPING || InterruptPending(INTERRUPT_WALSND_STOP)))
 		return;
 
 	/* Terminate immediately if the timeout is set to 0 */
@@ -3046,12 +3058,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	for (;;)
 	{
 		/* 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();
@@ -3100,13 +3112,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);
 		}
 
@@ -3165,7 +3177,11 @@ 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 |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3203,6 +3219,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3259,6 +3276,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = INVALID_PROC_NUMBER;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3723,7 +3741,7 @@ XLogSendLogical(void)
 	 * the pending data.
 	 */
 	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3848,10 +3866,9 @@ WalSndDone(WalSndSendDataCallback send_data)
 
 			/* Sleep until something happens or we time out */
 			WalSndWait(WL_SOCKET_WRITEABLE, sleeptime,
-					   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
-
-			/* Clear any already-pending wakeups */
-			ResetLatch(MyLatch);
+					   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+					   CheckForInterruptsMask |
+					   INTERRUPT_WALSND_INIT_STOPPING);
 
 			CHECK_FOR_INTERRUPTS();
 
@@ -3931,13 +3948,17 @@ WalSndRqstFileReload(void)
 }
 
 /*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
+ * Process INTERRUPT_INIT_STOPPING interrupt.
+ *
+ * Used to request a last cycle and shutdown.
  */
-void
-HandleWalSndInitStopping(void)
+static void
+ProcessWalSndInitStoppingInterrupt(void)
 {
 	Assert(am_walsender);
 
+	got_STOPPING = true;
+
 	/*
 	 * 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
@@ -3945,39 +3966,41 @@ HandleWalSndInitStopping(void)
 	 * standby, and then exit gracefully.
 	 */
 	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
+		ProcessTerminateInterrupt();
 	else
-		got_STOPPING = true;
-
-	/* latch will be set by procsignal_sigusr1_handler */
+		WalSndCheckShutdownTimeout();
 }
 
 /*
  * 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 */
+/* Set up walsender-specific signal and interrupt 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 */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	/*
+	 * 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);
+
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessWalSndInitStoppingInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 }
 
 /* Register shared-memory space needed by walsender */
@@ -4006,6 +4029,7 @@ WalSndShmemInit(void *arg)
 		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
 
 		SpinLockInit(&walsnd->mutex);
+		walsnd->pgprocno = INVALID_PROC_NUMBER;
 	}
 
 	ConditionVariableInit(&WalSndCtl->wal_flush_cv);
@@ -4050,24 +4074,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, InterruptMask 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
@@ -4115,16 +4143,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 1d4073ed0da..fbe46d6ee5c 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -391,7 +391,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 da7ed6ca53c..c31a3d92f39 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 94156f26eb4..a5f28672b0a 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -40,11 +40,9 @@
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -379,8 +377,8 @@ pgaio_worker_choose_idle(int only_workers_above)
 }
 
 /*
- * Try to wake a worker by setting its latch, to tell it there are IOs to
- * process in the submission queue.
+ * Try to wake a worker by sending it an interrupt, to tell it there are IOs
+ * to process in the submission queue.
  */
 static void
 pgaio_worker_wake(int worker)
@@ -396,7 +394,7 @@ pgaio_worker_wake(int worker)
 	 */
 	proc_number = io_worker_control->workers[worker].proc_number;
 	if (proc_number != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(proc_number)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, proc_number);
 }
 
 /*
@@ -678,20 +676,19 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	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, PG_SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, PG_SIG_IGN);
-	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -739,7 +736,7 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
 		int			worker = -1;
@@ -987,9 +984,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			set_ps_display(cmd);
 #endif
 
-			if (WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
-						  timeout_ms,
-						  WAIT_EVENT_IO_WORKER_MAIN) == WL_TIMEOUT)
+			if (WaitInterrupt(CheckForInterruptsMask |
+							  INTERRUPT_CONFIG_RELOAD |
+							  INTERRUPT_TERMINATE |
+							  INTERRUPT_WAIT_WAKEUP,
+							  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
+							  timeout_ms,
+							  WAIT_EVENT_IO_WORKER_MAIN) == WL_TIMEOUT)
 			{
 				/* WL_TIMEOUT */
 				if (pgaio_worker_can_timeout())
@@ -1005,14 +1006,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 					hist_ios /= 2;
 				}
 			}
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 
 			/* If io_max_workers has been decreased, exit highest first. */
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d6c0cc1f6d4..cafee156cfd 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -47,6 +47,7 @@
 #include "catalog/storage_xlog.h"
 #include "common/hashfn.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3449,7 +3450,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);
@@ -3629,7 +3630,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3710,7 +3711,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3801,7 +3802,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);
 	}
@@ -6794,12 +6795,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 fdb5bad7910..b31d5b50dbd 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"
@@ -206,27 +207,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);
 	}
 
 	/*
@@ -357,10 +356,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 f71653bbe48..332b625008b 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 651e9c23e16..0e86491e1b6 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -173,15 +173,13 @@ 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 hold interrupts to
+	 * prevent any more interrupts from being processed; we're doing our best
+	 * to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
-	InterruptHoldoffCount = 1;
-	CritSectionCount = 0;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
+	ResetInterruptHoldoffCounts(1, 0);
 
 	/*
 	 * Also clear the error context stack, to prevent error callbacks from
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 7d4f4cf32bb..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 initializing the shared memory block
- * containing the latch. (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 b8c31e29967..d744ef8dff6 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 60336b31803..cef20dfb9b0 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"
@@ -3458,7 +3459,7 @@ SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
 	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)
@@ -3466,7 +3467,7 @@ SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
 		(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;
 	}
 
@@ -3506,10 +3507,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;
 		}
@@ -3541,21 +3542,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 737ad81363c..e7969f661bc 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -30,8 +30,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"
@@ -41,28 +41,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.
@@ -72,7 +65,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) */
@@ -121,7 +113,6 @@ 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);
 
@@ -155,7 +146,6 @@ ProcSignalShmemInit(void *arg)
 		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);
@@ -185,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));
-
 	/*
 	 * Publish the PID before reading the global barrier generation to ensure
 	 * that EmitProcSignalBarrier() doesn't skip us while we are grabbing an
@@ -244,9 +231,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;
@@ -281,73 +269,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
@@ -392,8 +313,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
@@ -404,25 +325,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;
@@ -482,23 +390,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.
  *
@@ -514,12 +405,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
@@ -658,68 +545,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_SLOTSYNC_MESSAGE))
-		HandleSlotSyncMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -778,6 +604,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * XXX: 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 ef0195f9cfa..2607bae9a3b 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.
  *
@@ -46,10 +46,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 +115,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 +217,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 +235,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 +344,15 @@ shm_mq_send(shm_mq_handle *mqh, Size nbytes, const void *data, bool nowait,
  * Write a message into a shared message queue, gathered from multiple
  * addresses.
  *
- * When nowait = false, we'll wait on our process 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 +541,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 +559,15 @@ shm_mq_sendv(shm_mq_handle *mqh, shm_mq_iovec *iov, int iovcnt, bool nowait,
  * while still allowing longer messages.  In either case, the return value
  * remains valid until the next receive operation is performed on the queue.
  *
- * When nowait = false, we'll wait on our process 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 +620,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 +896,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 +994,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 +1002,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 +1010,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 +1056,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 +1152,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 +1256,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 +1303,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 84e44b398b2..0142a86485f 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -42,6 +42,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 +205,10 @@ 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);
-
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 1540c7e0696..2c664471c73 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,14 @@
 #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,37 +116,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;
-
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -172,15 +132,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
@@ -189,14 +149,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 37a21ffaf1a..fbc390e0118 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,10 @@
 #include <signal.h>
 #include <unistd.h>
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -119,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
@@ -316,6 +315,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 it by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -567,7 +574,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.
  *
@@ -660,8 +667,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)
@@ -672,8 +679,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 e58ac4c4363..01efae2516e 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -598,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
@@ -700,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
@@ -748,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:
@@ -768,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
@@ -840,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);
@@ -938,6 +957,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -947,6 +967,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -956,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 627dba0a842..07e222991f1 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 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
@@ -65,7 +66,6 @@
 #endif
 
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "portability/instr_time.h"
@@ -73,7 +73,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 +129,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
@@ -167,10 +167,8 @@ struct WaitEventSet
 #endif
 };
 
-#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
 
 #ifdef WAIT_USE_SIGNALFD
 /* On Linux, we'll receive SIGURG via a signalfd file descriptor. */
@@ -184,9 +182,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
 
@@ -204,6 +209,7 @@ static void WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event);
 static void WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event);
 #endif
 
+static void WaitEventSetWaitAbort(void);
 static inline int WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 										WaitEvent *occurred_events, int nevents);
 
@@ -236,7 +242,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 +292,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 +318,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -350,6 +356,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, PG_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 +425,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 +510,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 +549,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 +567,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,8 +576,8 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
-				  void *user_data)
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
+				  InterruptMask interruptMask, void *user_data)
 {
 	WaitEvent  *event;
 
@@ -581,19 +590,16 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 		set->exit_on_postmaster_death = true;
 	}
 
-	if (latch)
-	{
-		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
+	/*
+	 * 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 (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 +615,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 +652,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 +689,33 @@ 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 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 +745,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 +793,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 +856,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 +884,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 +899,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 +982,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)
 	{
@@ -1012,12 +1011,18 @@ WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event)
 		{
 			*handle = WSACreateEvent();
 			if (*handle == WSA_INVALID_EVENT)
+			{
+				WaitEventSetWaitAbort();
 				elog(ERROR, "failed to create event for socket: error code %d",
 					 WSAGetLastError());
+			}
 		}
 		if (WSAEventSelect(event->fd, *handle, flags) != 0)
+		{
+			WaitEventSetWaitAbort();
 			elog(ERROR, "failed to set up event for socket: error code %d",
 				 WSAGetLastError());
+		}
 
 		Assert(event->fd != PGINVALID_SOCKET);
 	}
@@ -1045,6 +1050,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);
 
@@ -1063,112 +1069,169 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 
 	pgstat_report_wait_start(wait_event_info);
 
-#ifndef WIN32
 	waiting = true;
-#else
-	/* Ensure that signals are serviced even if latch is already set */
+#ifdef WIN32
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * We will change the 'attention_mask' in MyPendingInterrupts for the
+	 * sleep, which means that CHECK_FOR_INTERRUPTS() won't work correctly.
+	 * There are no CHECK_FOR_INTERRUPTS() calls below, but hold interrupts
+	 * until we've restored 'attention_mask' just to be sure.
+	 */
+	HOLD_INTERRUPTS();
+
+	/*
+	 * 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;
+		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)
+		already_pending = InterruptPending(set->interrupt_mask);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
+			/*
+			 * Set the attention mask and SLEEPING bit and re-check if an
+			 * interrupt is already pending.  The memory barrier synchronizes
+			 * with the atomic fetch-or in SendOrRaiseInterrupt() so that if
+			 * an interrupt bit is set after setting the flag, the setter will
+			 * see the SLEEPING flag and will wake us up.
+			 */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+			pg_atomic_write_u64(&MyPendingInterrupts->attention_mask, set->interrupt_mask);
+#else
+			pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_lo, (uint32) set->interrupt_mask);
+			pg_atomic_write_u32(&MyPendingInterrupts->attention_mask_hi, (uint32) (set->interrupt_mask >> 32));
+#endif
+			pg_atomic_write_u32(&MyPendingInterrupts->flags, PI_FLAG_SLEEPING);
+
 			pg_memory_barrier();
-			/* and recheck */
+			already_pending = InterruptPending(set->interrupt_mask);
+
+			/* Remember to clear the SLEEPING flag afterwards. */
+			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);
+		for (;;)
+		{
+			int			rc;
 
-		if (set->latch &&
-			set->latch->maybe_sleeping)
-			set->latch->maybe_sleeping = false;
+			rc = WaitEventSetWaitBlock(set, cur_timeout,
+									   occurred_events, nevents - returned_events);
 
-		if (rc == -1)
-			break;				/* timeout occurred */
-		else
-			returned_events += rc;
+			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)
-		{
-			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)
+			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;
+			}
 		}
 	}
-#ifndef WIN32
+
+	/*
+	 * If we slept, clear the SLEEPING flag again and reset the attention mask
+	 * for CHECK_FOR_INTERRUPTS()
+	 */
+	if (sleeping_flag_armed)
+	{
+		pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+		SetInterruptAttentionMask(EnabledInterruptsMask);
+	}
 	waiting = false;
-#endif
+
+	RESUME_INTERRUPTS();
 
 	pgstat_report_wait_end();
 
 	return returned_events;
 }
 
+/*
+ * If WaitEventSetWaitBlock() errors out, it calls WaitEventSetWaitAbort() first to get
+ * us out of the waiting state.
+ */
+static void
+WaitEventSetWaitAbort(void)
+{
+	if (waiting)
+	{
+		/*
+		 * Clear the SLEEPING flag and reset attention mask for
+		 * CHECK_FOR_INTERRUPTS().  This is only necessary if we slept, but
+		 * there's no harm in doing it always and this error path isn't
+		 * performance sensitive.
+		 */
+		pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+		SetInterruptAttentionMask(EnabledInterruptsMask);
+
+		waiting = false;
+	}
+}
 
 #if defined(WAIT_USE_EPOLL)
 
@@ -1199,7 +1262,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		/* EINTR is okay, otherwise complain */
 		if (errno != EINTR)
 		{
-			waiting = false;
+			WaitEventSetWaitAbort();
 			ereport(ERROR,
 					(errcode_for_socket_access(),
 					 errmsg("%s() failed: %m",
@@ -1230,16 +1293,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 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1261,7 +1324,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!PostmasterIsAliveInternal())
 			{
 				if (set->exit_on_postmaster_death)
+				{
+					WaitEventSetWaitAbort();
 					proc_exit(1);
+				}
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_POSTMASTER_DEATH;
 				occurred_events++;
@@ -1343,7 +1409,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 	if (unlikely(set->report_postmaster_not_running))
 	{
 		if (set->exit_on_postmaster_death)
+		{
+			WaitEventSetWaitAbort();
 			proc_exit(1);
+		}
 		occurred_events->fd = PGINVALID_SOCKET;
 		occurred_events->events = WL_POSTMASTER_DEATH;
 		return 1;
@@ -1361,7 +1430,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		/* EINTR is okay, otherwise complain */
 		if (errno != EINTR)
 		{
-			waiting = false;
+			WaitEventSetWaitAbort();
 			ereport(ERROR,
 					(errcode_for_socket_access(),
 					 errmsg("%s() failed: %m",
@@ -1392,13 +1461,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 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1415,7 +1484,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			set->report_postmaster_not_running = true;
 
 			if (set->exit_on_postmaster_death)
+			{
+				WaitEventSetWaitAbort();
 				proc_exit(1);
+			}
 			occurred_events->fd = PGINVALID_SOCKET;
 			occurred_events->events = WL_POSTMASTER_DEATH;
 			occurred_events++;
@@ -1487,7 +1559,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		/* EINTR is okay, otherwise complain */
 		if (errno != EINTR)
 		{
-			waiting = false;
+			WaitEventSetWaitAbort();
 			ereport(ERROR,
 					(errcode_for_socket_access(),
 					 errmsg("%s() failed: %m",
@@ -1514,16 +1586,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 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1545,7 +1617,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!PostmasterIsAliveInternal())
 			{
 				if (set->exit_on_postmaster_death)
+				{
+					WaitEventSetWaitAbort();
 					proc_exit(1);
+				}
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_POSTMASTER_DEATH;
 				occurred_events++;
@@ -1622,6 +1697,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
@@ -1697,8 +1781,11 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 
 	/* Check return code */
 	if (rc == WAIT_FAILED)
+	{
+		WaitEventSetWaitAbort();
 		elog(ERROR, "WaitForMultipleObjects() failed: error code %lu",
 			 GetLastError());
+	}
 	else if (rc == WAIT_TIMEOUT)
 	{
 		/* timeout exceeded */
@@ -1727,19 +1814,18 @@ 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]))
+			{
+				WaitEventSetWaitAbort();
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
+			}
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1756,7 +1842,10 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!PostmasterIsAliveInternal())
 			{
 				if (set->exit_on_postmaster_death)
+				{
+					WaitEventSetWaitAbort();
 					proc_exit(1);
+				}
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_POSTMASTER_DEATH;
 				occurred_events++;
@@ -1774,8 +1863,11 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 
 			ZeroMemory(&resEvents, sizeof(resEvents));
 			if (WSAEnumNetworkEvents(cur_event->fd, handle, &resEvents) != 0)
+			{
+				WaitEventSetWaitAbort();
 				elog(ERROR, "failed to enumerate network events: error code %d",
 					 WSAGetLastError());
+			}
 			if ((cur_event->events & WL_SOCKET_READABLE) &&
 				(resEvents.lNetworkEvents & FD_READ))
 			{
@@ -1784,7 +1876,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 +1982,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 +2009,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;
@@ -1967,7 +2058,7 @@ drain(void)
 				continue;		/* retry */
 			else
 			{
-				waiting = false;
+				WaitEventSetWaitAbort();
 #ifdef WAIT_USE_SELF_PIPE
 				elog(ERROR, "read() on self-pipe failed: %m");
 #else
@@ -1977,7 +2068,7 @@ drain(void)
 		}
 		else if (rc == 0)
 		{
-			waiting = false;
+			WaitEventSetWaitAbort();
 #ifdef WAIT_USE_SELF_PIPE
 			elog(ERROR, "unexpected EOF on self-pipe");
 #else
@@ -2005,7 +2096,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2015,12 +2105,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 +2117,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 3a62a447aca..59307701f95 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -149,23 +149,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 +179,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 +270,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 +301,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 +335,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 +359,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 0ae85b7d5b4..cbc712196ea 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -200,6 +200,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"
@@ -1515,7 +1516,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;
@@ -3548,7 +3553,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 d9b0d8f62ce..669dc318900 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -243,6 +243,8 @@ ProcGlobalShmemInit(void *arg)
 	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);
 
@@ -314,14 +316,28 @@ ProcGlobalShmemInit(void *arg)
 		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);
 		}
 
@@ -532,13 +548,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);
@@ -704,13 +715,8 @@ InitAuxiliaryProcess(void)
 #endif
 	pg_atomic_write_u32(&MyProc->pendingRecoveryConflicts, 0);
 
-	/*
-	 * 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);
@@ -733,6 +739,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.
@@ -973,13 +983,9 @@ ProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/*
-	 * Reset MyLatch to the process local one and disown the shared latch, so
-	 * that signal handlers et al can continue using the latch after the
-	 * shared latch isn't ours anymore.
-	 *
-	 * DisownLatch() must happen before our PGPROC can appear on a freelist: a
-	 * newly-forked backend that pops our slot and calls OwnLatch() would
-	 * PANIC on a still-owned latch.
+	 * Reset interrupt vector to the process local one, so that signal
+	 * handlers et al can continue using interrupts after the PGPROC entry
+	 * isn't ours anymore.
 	 *
 	 * pgstat_reset_wait_event_storage() is intentionally deferred until after
 	 * the lock-group block so that wait_event_info remains visible in our
@@ -987,8 +993,7 @@ ProcKill(int code, Datum arg)
 	 * because our slot is not yet on any freelist at this point, and useful
 	 * for testing purposes.
 	 */
-	SwitchBackToLocalLatch();
-	DisownLatch(&MyProc->procLatch);
+	SwitchToLocalInterrupts();
 
 	proc = MyProc;
 	procgloballist = proc->procgloballist;
@@ -1046,7 +1051,7 @@ ProcKill(int code, Datum arg)
 		LWLockRelease(leader_lwlock);
 	}
 
-	/* See comment above, close to DisownLatch() */
+	/* See comment above, close to SwitchToLocalInterrupts() */
 	pgstat_reset_wait_event_storage();
 
 	MyProc = NULL;
@@ -1108,7 +1113,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 */
@@ -1127,11 +1132,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);
 
@@ -1460,18 +1474,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
 	{
@@ -1517,9 +1531,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)
 			{
@@ -1754,8 +1769,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)
 	{
@@ -1792,7 +1807,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.
  *
@@ -1821,7 +1836,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));
 }
 
 /*
@@ -1977,14 +1992,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;
 }
 
@@ -2063,34 +2076,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 a9b6c1ede6c..0bf6e48c45f 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..c5eb9931b09 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,11 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.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 +611,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 25205cee0fa..36290a11665 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 1c926928e9c..b808273c6dc 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -41,6 +41,7 @@
 #include "commands/prepare.h"
 #include "commands/repack.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
@@ -192,8 +193,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);
@@ -348,8 +349,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -366,11 +365,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();
 
@@ -477,7 +488,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;
 }
@@ -501,104 +514,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).
  *
@@ -2923,7 +2838,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 */
@@ -3020,15 +2935,12 @@ 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;
-
 		/*
 		 * Record who sent the signal.  Will be 0 on platforms without
 		 * SA_SIGINFO, which is fine -- ProcessInterrupts() checks for that.
@@ -3044,37 +2956,16 @@ die(SIGNAL_ARGS)
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	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 */
@@ -3091,22 +2982,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);
+
+	/*
+	 * 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);
+
+	/*
+	 * Note: Catchup interrupts (INTERRUPT_SINVAL_CATCHUP) must be handled in
+	 * anything that participates in shared invalidation.  But the handler for
+	 * that is set in sinvaladt.c.
+	 */
+
 }
 
 /*
- * 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)
 	{
@@ -3266,14 +3226,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if ((EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) == 0)
 			{
 				/*
-				 * Re-arm and defer this interrupt until later.  See similar
-				 * code in ProcessInterrupts().
+				 * Re-arm and defer this interrupt until later when we're
+				 * prepared to handle query cancellation.
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3304,77 +3264,35 @@ 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(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
 		int			sender_pid = ProcDieSenderPid;
 		int			sender_uid = ProcDieSenderUid;
 
-		ProcDiePending = false;
 		ProcDieSenderPid = 0;
 		ProcDieSenderUid = 0;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3430,65 +3348,55 @@ ProcessInterrupts(void)
 					 errmsg("terminating connection due to administrator command"),
 					 ERRDETAIL_SIGNAL_SENDER(sender_pid, sender_uid)));
 	}
+}
 
-	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.
@@ -3541,76 +3449,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)
-		ProcessParallelMessageInterrupt();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (SlotSyncShutdownPending)
-		ProcessSlotSyncMessage();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4280,8 +4178,9 @@ PostgresMain(const char *dbname, const char *username)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * 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
@@ -4294,13 +4193,18 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
 	if (am_walsender)
 		WalSndSignals();
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		/*
+		 * Cancel current query and exit on SIGTERM.  This is a bit more
+		 * complicated in backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest.
+		 */
+		pqsignal(SIGTERM, die);
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4314,7 +4218,6 @@ 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 */
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4323,11 +4226,39 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, PG_SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, PG_SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
+
+		/* Set handlers for various timeouts */
+		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);
 	}
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	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.)
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessageInterrupt);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4495,11 +4426,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 */
@@ -4680,7 +4614,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);
 
 				/*
@@ -4769,6 +4703,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)
 		 */
@@ -4798,22 +4754,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/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 1a4dbbeb8db..961511ec292 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -17,6 +17,7 @@
 
 #include "catalog/pg_type_d.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -268,7 +269,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.
@@ -297,14 +297,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 c033e68ba15..abed5888e45 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,7 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
 #include "parser/scansup.h"
@@ -38,7 +38,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"
@@ -348,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;
 
@@ -376,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 a20e7ea1d11..30e853ee36a 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1747,10 +1747,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 a6936a0c664..b886638e3a0 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -537,10 +537,7 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * could save and restore InterruptHoldoffCount for itself, but this
 		 * should make life easier for most.)
 		 */
-		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
-
-		CritSectionCount = 0;	/* should be unnecessary, but... */
+		ResetInterruptHoldoffCounts(0, 0);
 
 		/*
 		 * Note that we leave CurrentMemoryContext set to ErrorContext. The
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index b41dd0a6928..9ad03f8d665 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -30,20 +30,7 @@
 
 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;
+/* these are marked volatile because they are set by signal handlers: */
 volatile int ProcDieSenderPid = 0;
 volatile int ProcDieSenderUid = 0;
 
@@ -56,15 +43,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 f4992ff1622..7f93b133e78 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -43,7 +43,6 @@
 #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, PG_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 3d8c9bdebd5..f09a7231dee 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"
@@ -1413,20 +1414,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 ?
+	 */
 }
 
 /*
@@ -1435,51 +1435,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 ed54447d4a0..64df41b261a 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -14,11 +14,11 @@
  */
 #include "postgres.h"
 
+#include <signal.h>
 #include <sys/time.h>
 
 #include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -371,12 +371,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 27b339a649e..7a9efb79dd9 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1315,36 +1315,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/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 0ed0a88d831..fa42d343549 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -14,8 +14,6 @@
 #ifndef PARALLEL_H
 #define PARALLEL_H
 
-#include <signal.h>
-
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
@@ -55,7 +53,6 @@ typedef struct ParallelWorkerContext
 	shm_toc    *toc;
 } ParallelWorkerContext;
 
-extern PGDLLIMPORT volatile sig_atomic_t ParallelMessagePending;
 extern PGDLLIMPORT int ParallelWorkerNumber;
 extern PGDLLIMPORT bool InitializingParallelWorker;
 
@@ -72,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessageInterrupt(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 ba7750dca0b..f2cca911afb 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 202e4aa5e74..8e4894e59ab 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 void NotifyMyFrontEnd(const char *channel,
 							 const char *payload,
@@ -37,9 +34,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/commands/repack.h b/src/include/commands/repack.h
index 45e5440a311..a8437e64bee 100644
--- a/src/include/commands/repack.h
+++ b/src/include/commands/repack.h
@@ -13,8 +13,6 @@
 #ifndef REPACK_H
 #define REPACK_H
 
-#include <signal.h>
-
 #include "nodes/parsenodes.h"
 #include "parser/parse_node.h"
 #include "storage/lockdefs.h"
@@ -35,9 +33,6 @@ typedef struct ClusterParams
 	uint32		options;		/* bitmask of CLUOPT_* */
 } ClusterParams;
 
-extern PGDLLIMPORT volatile sig_atomic_t RepackMessagePending;
-
-
 extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel);
 
 extern void cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
@@ -58,7 +53,6 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
 							 MultiXactId cutoffMulti,
 							 char newrelpersistence);
 
-extern void HandleRepackMessageInterrupt(void);
 extern void ProcessRepackMessages(void);
 
 /* in repack_worker.c */
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
index b409ee18ddb..3876f11c3bc 100644
--- a/src/include/ipc/interrupt.h
+++ b/src/include/ipc/interrupt.h
@@ -15,8 +15,7 @@
 #ifndef IPC_INTERRUPT_H
 #define IPC_INTERRUPT_H
 
-#include <signal.h>
-
+#include "ipc/standard_interrupts.h"
 #include "port/atomics.h"
 #include "storage/procnumber.h"
 
@@ -29,80 +28,169 @@
  */
 #include "storage/waiteventset.h"
 
-/*****************************************************************************
- *	  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.
+/*
+ * PendingInterrupts is used to receive and wait for interrupts.  It contains
+ * a bitmask of interrupts pending for the process, and another bitmask of
+ * interrupts we're currently interested in.
  *
- * 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.
+ * We support up to 64 different interrupts.  That way, an interrupt mask can
+ * be conveniently stored as one 64-bit atomic integer, on systems with 64-bit
+ * atomics.  On other systems, it's split into two 32-bit atomic fields, which
+ * is good enough because we don't rely on atomicity between different
+ * interrupt bits.  (Note that the 64-bit atomics simulation relies on
+ * spinlocks, which creates a deadlock risk when used from signal handlers, so
+ * we cannot rely on the simulated 64-bit atomics.)
  *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
+ * Attention mechanism
+ * -------------------
  *
- * 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.
+ * The 'attention_mask' field lets a backend advertise which interrupts it is
+ * currently interested in.  When a backend is sleeping, waiting for an
+ * interrupt to arrive, it sets the bits for the waited-for interrupts in
+ * 'attention_mask'.  At other times, the 'attention_mask' equals
+ * EnabledInterruptsMask, i.e. the interrupts that can be processed by a
+ * CHECK_FOR_INTERRUPTS().
  *
- * 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.
+ * When a backend sets the interrupt bit of another backend (or the same
+ * backend), it also checks if that interrupt is in the target's
+ * 'attention_mask'.  If so, it sets the ATTENTION flag.  Furthermore, if the
+ * target backend is currently sleeping, i.e. if the SLEEPING flag is set, it
+ * also wakes it up.
  *
- * 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.
+ * When not sleeping, the ATTENTION flag is used as a quick check in
+ * CHECK_FOR_INTERRUPTS() for whether any interrupts need to be processed.
+ * Checking a single flag requires fewer instructions than checking the
+ * interrupt bits against EnabledInterruptsMask; the attention mechanism
+ * shifts that work to the sending backend.
  *
- * 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.
+ * There are race conditions in how the ATTENTION flag is set.  If a backend
+ * clears a bit from its 'attention_mask', and another backend is concurrently
+ * sending that interrupt, it's possible that the ATTENTION flag gets set or
+ * the process is woken up after the 'attention_mask' has already been
+ * cleared.  Because of that, the system needs to tolerate spuriously set
+ * ATTENTION flag and wakeups.  The operations are ordered so that the
+ * opposite is not possible: if you set a bit in the 'attention_mask' and then
+ * check that the bit is not set in the 'interrupts' mask, you are guaranteed
+ * to receive the attention flag or a wakeup if the interrupt is set later.
+ */
+typedef struct PendingInterrupts
+{
+	pg_atomic_uint32 flags;		/* PI_FLAG_* */
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_uint64 pending_mask;	/* pending interrupts */
+	pg_atomic_uint64 attention_mask;	/* interrupts that set the ATTENTION
+										 * flag */
+#else
+	pg_atomic_uint32 pending_mask_lo;
+	pg_atomic_uint32 pending_mask_hi;
+	pg_atomic_uint32 attention_mask_lo;
+	pg_atomic_uint32 attention_mask_hi;
+#endif
+} PendingInterrupts;
+
+#define PI_FLAG_ATTENTION		0x01
+#define PI_FLAG_SLEEPING		0x02
+
+/*
+ * Interrupt vector currently in use for this process.  Most of the time this
+ * points to MyProc->pendingInterrupts, but in processes that have no PGPROC
+ * entry (yet), it points to a process-private variable, so that interrupts
+ * can nevertheless be used from signal handlers in the same process.
+ */
+extern PGDLLIMPORT PendingInterrupts *MyPendingInterrupts;
+
+/*
+ * Test if an interrupt is pending
  *
- * 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.
+ * If 'interruptMask' has multiple bits set, returns true if any of them are
+ * pending.
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here, because 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.
+	 */
+	uint64		pending;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->pending_mask);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->pending_mask_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->pending_mask_hi) << 32;
+#endif
+
+	return (pending & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
  *
- *****************************************************************************/
+ * Note that this does not clear the ATTENTION flag, so if it was already set,
+ * the next CHECK_FOR_INTERRUPTS() will make an unnecessary but harmless
+ * ProcessInterrupts() call.
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	uint64		mask = ~interruptMask;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	(void) pg_atomic_fetch_and_u64(&MyPendingInterrupts->pending_mask, mask);
+#else
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->pending_mask_lo, (uint32) mask);
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->pending_mask_hi, (uint32) (mask >> 32));
+#endif
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (unlikely(InterruptPending(interruptMask)))
+	{
+		ClearInterrupt(interruptMask);
+		return true;
+	}
+	else
+		return false;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern void SendInterruptWithPid(InterruptMask interruptMask, ProcNumber pgprocno, pid_t pid);
+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);
 
-/* 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 int ProcDieSenderPid;
-extern PGDLLIMPORT volatile int ProcDieSenderUid;
-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;
 
 /*****************************************************************************
  *		CHECK_FOR_INTERRUPTS() and friends
  *****************************************************************************/
 
+/* Interrupts currently enabled for CHECK_FOR_INTERRUPTS() processing */
+extern PGDLLIMPORT InterruptMask EnabledInterruptsMask;
+
+/*
+ * Pointer to MyPendingInterrupts->flags, except when interrupt holdoff or a
+ * critical section prevents interrupts processing, in which case this points
+ * to a dummy all-zeros variable (ZeroPendingInterruptsFlags) instead.  This
+ * allows CHECK_FOR_INTERRUPTS() to follow just this one pointer, and not have
+ * to check the holdoff counts separately.
+ */
+extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterruptsFlags;
+
 /*
  * Check whether any enabled interrupt is pending, without trying to service
  * it immediately.  This is can be used in a HOLD_INTERRUPTS() block to check
@@ -110,21 +198,29 @@ extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
  */
 #ifndef WIN32
 #define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
+	(unlikely(InterruptPending(EnabledInterruptsMask)))
 #else
 #define INTERRUPTS_PENDING_CONDITION() \
 	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(EnabledInterruptsMask)))
 #endif
 
 /*
  * Can interrupts be processed in the current state, i.e. are the interrupts
- * not prevented by the HOLD_INTERRUPTS() or a critical section?
+ * not prevented by the HOLD_INTERRUPTS() or a critical section critical
+ * section?
  */
 #define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
+	(InterruptHoldoffCount == 0 && CritSectionCount == 0)
+
+/*
+ * 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.
+ */
+#define CheckForInterruptsMask \
+	(INTERRUPTS_CAN_BE_PROCESSED() ? EnabledInterruptsMask : 0)
 
 /*
  * Service an interrupt, if one is pending and it's safe to service it now.
@@ -132,28 +228,40 @@ extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
  * NB: This is called from all over the codebase, and in fairly tight loops,
  * so this needs to be very short and fast when there is no work to do!
  */
-#define CHECK_FOR_INTERRUPTS() \
+#define CHECK_FOR_INTERRUPTS()					\
 do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
+	if (unlikely(pg_atomic_read_u32(MyPendingInterruptsFlags) != 0)) \
+		ProcessInterrupts();											\
 } while(0)
 
-/* in tcop/postgres.c */
+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);
+
 extern void ProcessInterrupts(void);
+extern void SetInterruptAttentionMask(InterruptMask mask);
 
 /*****************************************************************************
  *		Critical section and interrupt holdoff mechanism
  *****************************************************************************/
 
-/* 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;
+extern PGDLLIMPORT uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT uint32 CritSectionCount;
+
+extern PGDLLIMPORT const pg_atomic_uint32 ZeroPendingInterruptsFlags;
 
 static inline void
 HOLD_INTERRUPTS(void)
 {
 	InterruptHoldoffCount++;
+
+	/*
+	 * Disable CHECK_FOR_INTERRUPTS() by pointing MyPendingInterruptsFlags to
+	 * an all-zeros constant.
+	 */
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
@@ -161,25 +269,15 @@ RESUME_INTERRUPTS(void)
 {
 	Assert(InterruptHoldoffCount > 0);
 	InterruptHoldoffCount--;
-}
-
-static inline void
-HOLD_CANCEL_INTERRUPTS(void)
-{
-	QueryCancelHoldoffCount++;
-}
-
-static inline void
-RESUME_CANCEL_INTERRUPTS(void)
-{
-	Assert(QueryCancelHoldoffCount > 0);
-	QueryCancelHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		MyPendingInterruptsFlags = &MyPendingInterrupts->flags;
 }
 
 static inline void
 START_CRIT_SECTION(void)
 {
 	CritSectionCount++;
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
@@ -187,6 +285,10 @@ END_CRIT_SECTION(void)
 {
 	Assert(CritSectionCount > 0);
 	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		MyPendingInterruptsFlags = &MyPendingInterrupts->flags;
 }
 
+extern void ResetInterruptHoldoffCounts(uint32 new_holdoff_count, uint32 new_crit_section_count);
+
 #endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/ipc/signal_handlers.h b/src/include/ipc/signal_handlers.h
index a499a271538..d4f0e58414f 100644
--- a/src/include/ipc/signal_handlers.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/ipc/standard_interrupts.h b/src/include/ipc/standard_interrupts.h
new file mode 100644
index 00000000000..4045cf1678e
--- /dev/null
+++ b/src/include/ipc/standard_interrupts.h
@@ -0,0 +1,212 @@
+/*-------------------------------------------------------------------------
+ *
+ * standard_interrupts.h
+ *	  List of built-in interrupt bits
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/standard_interrupts.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_STANDARD_INTERRUPTS_H
+#define IPC_STANDARD_INTERRUPTS_H
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
+/*
+ * Interrupt bits that used in PendingIntrrupts->pending_mask 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
+ *
+ * Also used to request a slotsync worker or some other backend syncing
+ * replication slots to stop syncing.
+ */
+#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 64
+
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handling functions.  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);
+
+#endif							/* IPC_STANDARD_INTERRUPTS_H */
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 019e6c710e4..278149e3a03 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -32,9 +32,7 @@
 
 #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_classes.h"
 
@@ -199,8 +197,8 @@ libpqsrv_connect_complete(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();
 	{
@@ -231,18 +229,15 @@ libpqsrv_connect_complete(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)
@@ -352,19 +347,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)
@@ -418,7 +410,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)
@@ -447,10 +439,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 d15073a0a93..465a4cdf45f 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 5a1b6524673..532e8f8606a 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -62,11 +62,13 @@ extern PGDLLIMPORT int serializable_buffers;
 extern PGDLLIMPORT int subtransaction_buffers;
 extern PGDLLIMPORT int transaction_buffers;
 
+extern PGDLLIMPORT volatile int ProcDieSenderPid;
+extern PGDLLIMPORT volatile int ProcDieSenderUid;
+
 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;
@@ -199,9 +201,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 b6261bc01df..39969dce368 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 */
 
@@ -45,9 +46,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 f6994e9a1a1..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);
diff --git a/src/include/replication/slotsync.h b/src/include/replication/slotsync.h
index a55d1f0dccc..d2121cd3ed7 100644
--- a/src/include/replication/slotsync.h
+++ b/src/include/replication/slotsync.h
@@ -12,15 +12,10 @@
 #ifndef SLOTSYNC_H
 #define SLOTSYNC_H
 
-#include <signal.h>
-
 #include "replication/walreceiver.h"
 
 extern PGDLLIMPORT bool sync_replication_slots;
 
-/* Interrupt flag set by HandleSlotSyncMessageInterrupt() */
-extern PGDLLIMPORT volatile sig_atomic_t SlotSyncShutdownPending;
-
 /*
  * GUCs needed by slot sync worker to connect to the primary
  * server and carry on with slots synchronization.
@@ -37,7 +32,5 @@ extern void ShutDownSlotSync(void);
 extern bool SlotSyncWorkerCanRestart(void);
 extern bool IsSyncingReplicationSlots(void);
 extern void SyncReplicationSlots(WalReceiverConn *wrconn);
-extern void HandleSlotSyncMessageInterrupt(void);
-extern void ProcessSlotSyncMessage(void);
 
 #endif							/* SLOTSYNC_H */
diff --git a/src/include/replication/walreceiver.h b/src/include/replication/walreceiver.h
index 47c07574d4d..94988ff6267 100644
--- a/src/include/replication/walreceiver.h
+++ b/src/include/replication/walreceiver.h
@@ -13,6 +13,7 @@
 #define _WALRECEIVER_H
 
 #include <netdb.h>
+#include <signal.h>
 
 #include "access/xlog.h"
 #include "access/xlogdefs.h"
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index 386cedfc7aa..bc9a3bfabe4 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -45,7 +45,6 @@ extern void WalSndSignals(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 b0c80deeb24..3e1453f1d89 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 745b7d9e969..e7d7606666b 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -78,9 +78,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index 053a32ecf9d..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,142 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.h
- *	  Routines for interprocess latches
- *
- * A latch is a boolean variable, with operations that let processes sleep
- * until it is set. A latch can be set from another process, or a signal
- * handler within the same process.
- *
- * 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. Because on some platforms an
- * incoming signal doesn't interrupt sleep, and even on platforms where it
- * does there is a race condition if the signal arrives just before
- * entering the sleep, the common pattern must periodically wake up and
- * poll the flag variable. The pselect() system call was invented to solve
- * this problem, but it is not portable enough. Latches are designed to
- * overcome these limitations, allowing you to sleep without polling and
- * ensuring quick response to signals from other processes.
- *
- * There are two kinds of latches: local and shared. A local latch is
- * initialized by InitLatch, and can only be set from the same process.
- * A local latch can be used to wait for a signal to arrive, by calling
- * SetLatch in the signal handler. A shared latch resides in shared memory,
- * and must be initialized at postmaster startup by InitSharedLatch. Before
- * a shared latch can be waited on, it must be associated with a process
- * with OwnLatch. Only the process owning the latch can wait on it, but any
- * process can set it.
- *
- * There are three basic operations on a latch:
- *
- * SetLatch		- Sets the latch
- * ResetLatch	- Clears the latch, allowing it to be set again
- * WaitLatch	- Waits for the latch to become set
- *
- * WaitLatch 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 latch.c for detailed specifications for the exported functions.
- *
- * The correct pattern to wait for event(s) is:
- *
- * for (;;)
- * {
- *	   ResetLatch();
- *	   if (work to do)
- *		   Do Stuff();
- *	   WaitLatch();
- * }
- *
- * It's important to reset the latch *before* checking if there's work to
- * do. Otherwise, if someone sets the latch between the check and the
- * ResetLatch call, you will miss it and Wait will incorrectly block.
- *
- * Another valid coding pattern looks like:
- *
- * for (;;)
- * {
- *	   if (work to do)
- *		   Do Stuff(); // in particular, exit loop if some condition satisfied
- *	   WaitLatch();
- *	   ResetLatch();
- * }
- *
- * This is useful to reduce latch 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
- * WaitLatch and before ResetLatch, as that creates a race condition.
- *
- * To wake up the waiter, you must first set a global flag or something
- * else that the wait loop tests in the "if (work to do)" part, and call
- * SetLatch *after* that. SetLatch is designed to return quickly if the
- * latch is already set.
- *
- * On some platforms, signals will not interrupt the latch wait primitive
- * by themselves.  Therefore, it is critical that any signal handler that
- * is meant to terminate a WaitLatch wait calls SetLatch.
- *
- * Note that use of the process latch (PGPROC.procLatch) is generally better
- * than an ad-hoc shared latch for signaling auxiliary processes.  This is
- * because generic signal handlers will call SetLatch on the process latch
- * only, so using any latch other than the process latch effectively precludes
- * use of any generic handler.
- *
- *
- * See also WaitEventSets in waiteventset.h. They allow to wait for latches
- * 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.
- *
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/storage/latch.h
- *
- *-------------------------------------------------------------------------
- */
-#ifndef LATCH_H
-#define LATCH_H
-
-#include <signal.h>
-
-#include "storage/waiteventset.h"	/* for WL_* arguments to WaitLatch */
-#include "utils/wait_classes.h"  /* for backward compatibility */	/* IWYU pragma: keep */
-
-
-/*
- * Latch structure should be treated as opaque and only accessed through
- * the public functions. It is defined here to allow embedding Latches as
- * part of bigger structs.
- */
-typedef struct Latch
-{
-	sig_atomic_t is_set;
-	sig_atomic_t maybe_sleeping;
-	bool		is_shared;
-	int			owner_pid;
-#ifdef WIN32
-	HANDLE		event;
-#endif
-} Latch;
-
-/*
- * prototypes for functions in latch.c
- */
-extern void InitLatch(Latch *latch);
-extern void InitSharedLatch(Latch *latch);
-extern void OwnLatch(Latch *latch);
-extern void DisownLatch(Latch *latch);
-extern void SetLatch(Latch *latch);
-extern void ResetLatch(Latch *latch);
-
-extern int	WaitLatch(Latch *latch, int wakeEvents, long timeout,
-					  uint32 wait_event_info);
-extern int	WaitLatchOrSocket(Latch *latch, int wakeEvents,
-							  pgsocket sock, long timeout, uint32 wait_event_info);
-extern void InitializeLatchWaitSet(void);
-
-#endif							/* LATCH_H */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 0314f979e60..0202a94ed05 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -15,9 +15,9 @@
 #define _PROC_H_
 
 #include "access/xlogdefs.h"
+#include "ipc/interrupt.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"
@@ -259,12 +259,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 */
+	PendingInterrupts 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 +500,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;
@@ -575,9 +583,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/procsignal.h b/src/include/storage/procsignal.h
index 691314e26bf..494033c94df 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_SLOTSYNC_MESSAGE,	/* ask slot synchronization to stop */
-	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 */
@@ -68,16 +39,12 @@ typedef enum
  * prototypes for functions in procsignal.c
  */
 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..390a48447bf 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -14,8 +14,6 @@
 #ifndef SINVAL_H
 #define SINVAL_H
 
-#include <signal.h>
-
 #include "storage/relfilelocator.h"
 
 /*
@@ -137,16 +135,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 b18e4815bfc..c8f93b04d7d 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,11 +24,14 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_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() */
@@ -68,30 +70,32 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 struct ResourceOwnerData;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
 
 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 0d4aebfd543..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -69,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 38036c7c703..8ddca8add87 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/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/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_checksums/test_checksums.c b/src/test/modules/test_checksums/test_checksums.c
index 621cf788dad..1e7bdeef067 100644
--- a/src/test/modules/test_checksums/test_checksums.c
+++ b/src/test/modules/test_checksums/test_checksums.c
@@ -15,7 +15,6 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "postmaster/datachecksum_state.h"
-#include "storage/latch.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
 
@@ -32,10 +31,7 @@ dc_delay_barrier(const char *name, const void *private_data, void *arg)
 	(void) name;
 	(void) private_data;
 
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 (3 * 1000),
-					 WAIT_EVENT_PG_SLEEP);
+	pg_usleep(3 * 1000 * 1000); /* 3 seconds */
 }
 
 PG_FUNCTION_INFO_V1(dcw_inject_delay_barrier);
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 259c0c8253a..f9f17bd568a 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -16,7 +16,6 @@
 #include "postgres.h"
 
 #include "ipc/interrupt.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "storage/proc.h"
@@ -136,6 +135,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;
@@ -225,8 +225,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)
@@ -267,6 +265,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;
@@ -289,11 +290,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 9f401d30176..de420d8a076 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -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..6bb201166bd 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -15,6 +15,7 @@
 #define TEST_SHM_MQ_H
 
 #include "storage/dsm.h"
+#include "storage/procnumber.h"
 #include "storage/shm_mq.h"
 #include "storage/spin.h"
 
@@ -28,6 +29,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 1b22515ec5c..98ad61fc800 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -21,7 +21,6 @@
 
 #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 bd9002bf41d..359555b16a6 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 "ipc/signal_handlers.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.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(uint32));
 
-	/* 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/test/perl/PostgreSQL/Test/Cluster.pm b/src/test/perl/PostgreSQL/Test/Cluster.pm
index 529f49efee1..611416b89c2 100644
--- a/src/test/perl/PostgreSQL/Test/Cluster.pm
+++ b/src/test/perl/PostgreSQL/Test/Cluster.pm
@@ -3355,7 +3355,7 @@ querying some intermediate replication node rather than the primary.
 
 When the standby is passed as a PostgreSQL::Test::Cluster instance and the
 mode is replay, write, or flush, the function uses WAIT FOR LSN on the
-standby for latch-based wakeup instead of polling.  If the standby has been
+standby for interrupt-based wakeup instead of polling.  If the standby has been
 promoted, if the session is interrupted by a recovery conflict, or if the
 standby is unreachable, it falls back to polling.
 
@@ -3417,7 +3417,7 @@ sub wait_for_catchup
 	# - The mode is replay, write, or flush (not 'sent')
 	# - There is no sparc64+ext4 bug
 	# This is more efficient than polling pg_stat_replication on the upstream,
-	# as WAIT FOR LSN uses a latch-based wakeup mechanism.
+	# as WAIT FOR LSN uses a interrupt-based wakeup mechanism.
 	#
 	# We skip the pg_is_in_recovery() pre-check and just attempt WAIT FOR
 	# LSN directly.  If the standby was promoted, it returns 'not_in_recovery'
diff --git a/src/test/recovery/t/049_wait_for_lsn.pl b/src/test/recovery/t/049_wait_for_lsn.pl
index bc216064714..b83a39776f0 100644
--- a/src/test/recovery/t/049_wait_for_lsn.pl
+++ b/src/test/recovery/t/049_wait_for_lsn.pl
@@ -860,7 +860,7 @@ $arc_primary->poll_query_until('postgres',
   or die "Timed out waiting for WAL archiving on arc_primary (round 2)";
 
 # Start background waiters.  With replay paused, target > replay, so they
-# will sleep on WaitLatch.  They can only be woken by the replay-loop
+# will sleep on WaitInterrupt.  They can only be woken by the replay-loop
 # WaitLSNWakeup calls.
 my $arc_write_session = $arc_standby->background_psql('postgres');
 $arc_write_session->query_until(
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index c5db6ca6705..ca66234ad32 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1381,6 +1381,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1619,7 +1620,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2088,6 +2088,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
@@ -2247,6 +2248,7 @@ PatternInfoArray
 Pattern_Prefix_Status
 Pattern_Type
 PendingFsyncEntry
+PendingInterrupts
 PendingListenAction
 PendingListenEntry
 PendingRelDelete
-- 
2.47.3

