public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v38 04/11] Add Incremental View Maintenance support to pg_dump
22+ messages / 5 participants
[nested] [flat]

* [PATCH v38 04/11] Add Incremental View Maintenance support to pg_dump
@ 2020-11-11 08:01 Yugo Nagata <[email protected]>
  0 siblings, 0 replies; 22+ messages in thread

From: Yugo Nagata @ 2020-11-11 08:01 UTC (permalink / raw)

Support CREATE INCREMENTAL MATERIALIZED VIEW syntax.
---
 src/bin/pg_dump/pg_dump.c        | 18 +++++++++++++++---
 src/bin/pg_dump/pg_dump.h        |  2 ++
 src/bin/pg_dump/t/002_pg_dump.pl | 18 ++++++++++++++++++
 3 files changed, 35 insertions(+), 3 deletions(-)

diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index c56437d6057..f7420e0db41 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -7323,6 +7323,7 @@ getTables(Archive *fout, int *numTables)
 	int			i_relacl;
 	int			i_acldefault;
 	int			i_ispartition;
+	int			i_isivm;
 
 	/*
 	 * Find all the tables and table-like objects.
@@ -7432,10 +7433,17 @@ getTables(Archive *fout, int *numTables)
 
 	if (fout->remoteVersion >= 100000)
 		appendPQExpBufferStr(query,
-							 "c.relispartition AS ispartition ");
+							 "c.relispartition AS ispartition, ");
 	else
 		appendPQExpBufferStr(query,
-							 "false AS ispartition ");
+							 "false AS ispartition, ");
+
+	if (fout->remoteVersion >= 200000)
+		appendPQExpBufferStr(query,
+							 "c.relisivm AS isivm ");
+	else
+		appendPQExpBufferStr(query,
+							 "false AS isivm ");
 
 	/*
 	 * Left join to pg_depend to pick up dependency info linking sequences to
@@ -7548,6 +7556,7 @@ getTables(Archive *fout, int *numTables)
 	i_relacl = PQfnumber(res, "relacl");
 	i_acldefault = PQfnumber(res, "acldefault");
 	i_ispartition = PQfnumber(res, "ispartition");
+	i_isivm = PQfnumber(res, "isivm");
 
 	if (dopt->lockWaitTimeout)
 	{
@@ -7630,6 +7639,7 @@ getTables(Archive *fout, int *numTables)
 			tblinfo[i].amname = pg_strdup(PQgetvalue(res, i, i_amname));
 		tblinfo[i].is_identity_sequence = (strcmp(PQgetvalue(res, i, i_is_identity_sequence), "t") == 0);
 		tblinfo[i].ispartition = (strcmp(PQgetvalue(res, i, i_ispartition), "t") == 0);
+		tblinfo[i].isivm = (strcmp(PQgetvalue(res, i, i_isivm), "t") == 0);
 
 		/* other fields were zeroed above */
 
@@ -17452,10 +17462,12 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo)
 		 * PostgreSQL 18 has disabled UNLOGGED for partitioned tables, so
 		 * ignore it when dumping if it was set in this case.
 		 */
-		appendPQExpBuffer(q, "CREATE %s%s %s",
+		appendPQExpBuffer(q, "CREATE %s%s%s %s",
 						  (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED &&
 						   tbinfo->relkind != RELKIND_PARTITIONED_TABLE) ?
 						  "UNLOGGED " : "",
+						  tbinfo->relkind == RELKIND_MATVIEW && tbinfo->isivm ?
+						  "INCREMENTAL " : "",
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 5a6726d8b12..4408c504323 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -347,6 +347,8 @@ typedef struct _tableInfo
 	int			numParents;		/* number of (immediate) parent tables */
 	struct _tableInfo **parents;	/* TableInfos of immediate parents */
 
+	bool		isivm;			/* is incrementally maintainable materialized view? */
+
 	/*
 	 * These fields are computed only if we decide the table is interesting
 	 * (it's either a table to dump, or a direct parent of a dumpable table).
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index 9258948b583..8abde57e4f3 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -3033,6 +3033,24 @@ my %tests = (
 		},
 	},
 
+	'CREATE MATERIALIZED VIEW matview_ivm' => {
+		create_order => 21,
+		create_sql   => 'CREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm (col1) AS
+					   SELECT col1 FROM dump_test.test_table;',
+		regexp => qr/^
+			\QCREATE INCREMENTAL MATERIALIZED VIEW dump_test.matview_ivm AS\E
+			\n\s+\QSELECT col1\E
+			\n\s+\QFROM dump_test.test_table\E
+			\n\s+\QWITH NO DATA;\E
+			/xm,
+		like =>
+		  { %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
+		unlike => {
+			exclude_dump_test_schema => 1,
+			only_dump_measurement => 1,
+		},
+	},
+
 	'CREATE POLICY p1 ON test_table' => {
 		create_order => 22,
 		create_sql => 'CREATE POLICY p1 ON dump_test.test_table
-- 
2.43.0


--Multipart=_Wed__1_Jul_2026_00_04_01_+0900_OVSy2WWK_9aByzDJ
Content-Type: text/x-diff;
 name="v38-0003-Allow-to-prolong-life-span-of-transition-tables-.patch"
Content-Disposition: attachment;
 filename="v38-0003-Allow-to-prolong-life-span-of-transition-tables-.patch"
Content-Transfer-Encoding: 7bit



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
@ 2024-12-02 10:42 Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2024-12-02 10:42 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers; Andres Freund <[email protected]>

On 02/12/2024 09:32, Thomas Munro wrote:
> On Sat, Nov 23, 2024 at 10:58 AM Heikki Linnakangas <[email protected]> wrote:
>> Hmm, so this would replace the maybeSleepingOnInterrupts bitmask I
>> envisioned. Makes a lot of sense. If it's a single bit though, that
>> means that you'll still get woken up by interrupts that you're not
>> waiting for. Maybe that's fine. Or we could merge the
>> maybeSleepingOnInterrupts and pendingInterrupts bitmasks to a single
>> atomic word, so that you would have a separate "maybe sleeping" bit for
>> each interrupt bit, but could still use atomic_fetch_or atomically read
>> the interrupt bits and announce the sleeping.
> 
> I think one bit is fine for now.  At least, until we have a serious
> problem with interrupts arriving when you're sleeping but not ready to
> service that particular interrupt.  The 'interrupt bit already set,
> don't try to wake me' stuff discussed earlier would limit the number
> of useless wakeups to one, until you eventually are ready and consume
> the interrupt.  The main case I can think of, if we fast forward to
> the all-procsignals-become-interrupts patch (which I'll be rebasing on
> top of this when the next version appears), is that you might receive
> a sinval catchup request, but you might be busy running a long query.
> Sinval catchup messages are only processed between queries, so you
> just keep ignoring them until end of query.  I think that's fine, and
> unlikely.  Do you have other cases in mind?

Yeah, no, I think one bit is is good enough. Let's go with that.

> If there is legitimate use case for a more fine-grained maybe-sleeping
> and I've been too optimistic above, I don't think we should give one
> whole maybe-sleeping bit to each interrupt reason.  We only have 32
> bit atomics (splinlock-based emulation of 64 bit atomics is not good
> enough for this, it's not safe in SIGALRM handlers, at least not
> without a lot more pain; admittedly the SIGALRM handlers should
> eventually be replaced but not for a while) so if we used up two bits
> for every interrupt reason we could handle only 16 interrupt reasons,
> and that's already not enough.  Perhaps we could add maybe-sleeping
> bits for classes of interrupt if we ever determine that one bit for
> all of them isn't enough?

If we run out of bits in a single pendingInterrupt words, we can have 
multiple words. SendInterrupt and ClearInterrupt would still only need 
to manipulate one word, the one holding the bit it's setting/clearing. 
WaitEventSetWait() would need to touch all of them, or at least all the 
ones that hold bits you want to wait for. That seems OK from a 
performance point of view.

I don't think we need to go there any time soon though, 32 bits should 
be enough for the use cases we've been discussing.

-- 
Heikki Linnakangas
Neon (https://neon.tech)







^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2024-12-02 14:39 ` Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2024-12-02 14:39 UTC (permalink / raw)
  To: Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers; Andres Freund <[email protected]>

On 02/12/2024 12:42, Heikki Linnakangas wrote:
> On 02/12/2024 09:32, Thomas Munro wrote:
>> On Sat, Nov 23, 2024 at 10:58 AM Heikki Linnakangas <[email protected]> 
>> wrote:
>>> Hmm, so this would replace the maybeSleepingOnInterrupts bitmask I
>>> envisioned. Makes a lot of sense. If it's a single bit though, that
>>> means that you'll still get woken up by interrupts that you're not
>>> waiting for. Maybe that's fine. Or we could merge the
>>> maybeSleepingOnInterrupts and pendingInterrupts bitmasks to a single
>>> atomic word, so that you would have a separate "maybe sleeping" bit for
>>> each interrupt bit, but could still use atomic_fetch_or atomically read
>>> the interrupt bits and announce the sleeping.
>>
>> I think one bit is fine for now.  At least, until we have a serious
>> problem with interrupts arriving when you're sleeping but not ready to
>> service that particular interrupt.  The 'interrupt bit already set,
>> don't try to wake me' stuff discussed earlier would limit the number
>> of useless wakeups to one, until you eventually are ready and consume
>> the interrupt.  The main case I can think of, if we fast forward to
>> the all-procsignals-become-interrupts patch (which I'll be rebasing on
>> top of this when the next version appears), is that you might receive
>> a sinval catchup request, but you might be busy running a long query.
>> Sinval catchup messages are only processed between queries, so you
>> just keep ignoring them until end of query.  I think that's fine, and
>> unlikely.  Do you have other cases in mind?
> 
> Yeah, no, I think one bit is is good enough. Let's go with that.

Here's a new patch set version, with the following changes:

- Implement the "maybe sleeping" flag as a single bit in the pending 
interrupts mask, per above discussion. One notable change is that I 
moved the check for whether an interrupt is set out of the loop in 
WaitEventSetWait(). It seemed redundant; all the WaitEventSetWaitBlock() 
implementations also check the interrupt mask if the wakeup is received. 
I'm sure it doesn't make a difference from performance point of view, 
but it feels more natural to me this way.

- Suppress the wakeup in SendInterrupt if the interrupt was already 
pending, per discussion

- Rename INTERRUPT_GENERAL_WAKEUP to INTERRUPT_GENERAL per Robert's 
suggestion

- Fix a bunch of minor comment issues, some pointed out off-list by 
Álvaro (thanks!)

-- 
Heikki Linnakangas
Neon (https://neon.tech)

Attachments:

  [text/x-patch] v5-0001-Replace-Latches-with-Interrupts.patch (221.1K, ../../[email protected]/2-v5-0001-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From eff8de11fbfea4e2aadce9c1d71452b0f5a1b80b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:51:54 +0200
Subject: [PATCH v5 1/4] Replace Latches with Interrupts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

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

This commit introduces two interrupt bits. INTERRUPT_GENERAL replaces
the general-purpose per-process latch. All code that previously set a
process's process latch now sets its INTERRUPT_GENERAL interrupt bit
instead.

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

More interrupt bits are planned for followup patches, to replace the
various ProcSignal bits, as well as ConfigReloadPending and
ShutdownRequestPending.

This also moves the WaitEventSet functions to a different source file,
waiteventset.c. This separates the platform-dependent code waiting and
signalling code from the platform-independent parts.

Reviewed-by: Thomas Munro, Robert Haas, Álvaro Herrera
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 contrib/pg_prewarm/autoprewarm.c              |  20 +-
 contrib/postgres_fdw/connection.c             |  22 +-
 contrib/postgres_fdw/postgres_fdw.c           |   4 +-
 doc/src/sgml/bgworker.sgml                    |   2 +-
 doc/src/sgml/sources.sgml                     |   6 +-
 src/backend/access/heap/vacuumlazy.c          |  11 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/parallel.c         |  30 +-
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/access/transam/xlogfuncs.c        |  12 +-
 src/backend/access/transam/xlogrecovery.c     |  91 +--
 src/backend/access/transam/xlogutils.c        |   6 +-
 src/backend/backup/basebackup_throttle.c      |  18 +-
 src/backend/commands/async.c                  |  19 +-
 src/backend/commands/vacuum.c                 |   4 +-
 src/backend/executor/nodeAppend.c             |   5 +-
 src/backend/executor/nodeGather.c             |   8 +-
 src/backend/libpq/auth.c                      |   9 +-
 src/backend/libpq/be-secure-gssapi.c          |  15 +-
 src/backend/libpq/be-secure-openssl.c         |   7 +-
 src/backend/libpq/be-secure.c                 |  29 +-
 src/backend/libpq/pqcomm.c                    |  29 +-
 src/backend/libpq/pqmq.c                      |   8 +-
 src/backend/libpq/pqsignal.c                  |   2 +-
 src/backend/postmaster/autovacuum.c           |  22 +-
 src/backend/postmaster/auxprocess.c           |   1 +
 src/backend/postmaster/bgworker.c             |  18 +-
 src/backend/postmaster/bgwriter.c             |  37 +-
 src/backend/postmaster/checkpointer.c         |  24 +-
 src/backend/postmaster/interrupt.c            |   6 +-
 src/backend/postmaster/pgarch.c               |  27 +-
 src/backend/postmaster/postmaster.c           |  33 +-
 src/backend/postmaster/startup.c              |  11 +-
 src/backend/postmaster/syslogger.c            |  18 +-
 src/backend/postmaster/walsummarizer.c        |  24 +-
 src/backend/postmaster/walwriter.c            |  23 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |  32 +-
 .../replication/logical/applyparallelworker.c |  39 +-
 src/backend/replication/logical/launcher.c    |  54 +-
 src/backend/replication/logical/slotsync.c    |  23 +-
 src/backend/replication/logical/tablesync.c   |  35 +-
 src/backend/replication/logical/worker.c      |  25 +-
 src/backend/replication/syncrep.c             |  29 +-
 src/backend/replication/walreceiver.c         |  47 +-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           |  27 +-
 src/backend/storage/buffer/bufmgr.c           |  10 +-
 src/backend/storage/buffer/freelist.c         |  27 +-
 src/backend/storage/ipc/Makefile              |   5 +-
 src/backend/storage/ipc/interrupt.c           | 125 ++++
 src/backend/storage/ipc/meson.build           |   3 +-
 src/backend/storage/ipc/procsignal.c          |   6 +-
 src/backend/storage/ipc/shm_mq.c              | 102 +--
 src/backend/storage/ipc/signalfuncs.c         |  11 +-
 src/backend/storage/ipc/sinval.c              |  10 +-
 src/backend/storage/ipc/standby.c             |  22 +-
 .../storage/ipc/{latch.c => waiteventset.c}   | 676 +++++++-----------
 src/backend/storage/lmgr/condition_variable.c |  34 +-
 src/backend/storage/lmgr/predicate.c          |   8 +-
 src/backend/storage/lmgr/proc.c               | 127 ++--
 src/backend/storage/sync/sync.c               |   6 +-
 src/backend/tcop/postgres.c                   |  25 +-
 src/backend/utils/adt/misc.c                  |  26 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/init/globals.c              |   9 -
 src/backend/utils/init/miscinit.c             |  60 +-
 src/backend/utils/init/postinit.c             |  11 +-
 src/backend/utils/misc/timeout.c              |   8 +-
 src/backend/utils/mmgr/mcxt.c                 |   2 +-
 src/include/access/parallel.h                 |   2 +
 src/include/libpq/libpq-be-fe-helpers.h       |  45 +-
 src/include/libpq/libpq.h                     |   4 +-
 src/include/miscadmin.h                       |   4 -
 src/include/storage/interrupt.h               | 171 +++++
 src/include/storage/latch.h                   | 196 -----
 src/include/storage/proc.h                    |  17 +-
 src/include/storage/waiteventset.h            | 120 ++++
 src/port/pgsleep.c                            |   8 +-
 src/test/isolation/isolationtester.c          |   2 +-
 src/test/modules/test_shm_mq/setup.c          |   9 +-
 src/test/modules/test_shm_mq/test.c           |  13 +-
 src/test/modules/test_shm_mq/worker.c         |   3 +-
 src/test/modules/worker_spi/worker_spi.c      |  20 +-
 src/tools/pgindent/typedefs.list              |   2 +-
 84 files changed, 1455 insertions(+), 1409 deletions(-)
 create mode 100644 src/backend/storage/ipc/interrupt.c
 rename src/backend/storage/ipc/{latch.c => waiteventset.c} (76%)
 create mode 100644 src/include/storage/interrupt.h
 delete mode 100644 src/include/storage/latch.h
 create mode 100644 src/include/storage/waiteventset.h

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index fac4051e1a..7c291c996c 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -37,8 +37,8 @@
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/procsignal.h"
 #include "storage/smgr.h"
@@ -216,10 +216,10 @@ autoprewarm_main(Datum main_arg)
 		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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								 -1L,
+								 PG_WAIT_EXTENSION);
 		}
 		else
 		{
@@ -243,14 +243,14 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 delay_in_ms,
+								 PG_WAIT_EXTENSION);
 		}
 
 		/* Reset the latch, loop. */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	/*
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 2326f391d3..42fdb59794 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -26,7 +26,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -731,8 +731,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(ERROR, NULL, conn, false, sql);
@@ -1367,7 +1367,7 @@ pgfdw_cancel_query_end(PGconn *conn, TimestampTz endtime, bool consume_input)
 	/*
 	 * 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))
@@ -1461,7 +1461,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))
@@ -1541,12 +1541,12 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime, PGresult **result,
 					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(1 << INTERRUPT_GENERAL,
+										   WL_INTERRUPT | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+										   PQsocket(conn),
+										   cur_timeout, pgfdw_we_cleanup_result);
+				ClearInterrupt(INTERRUPT_GENERAL);
 
 				CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index c0810fbd7c..0a699f2f76 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -38,7 +38,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7354,7 +7354,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 2c393385a9..3171054e55 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -227,7 +227,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.
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index fa68d4d024..fc642f5f71 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -989,19 +989,19 @@ 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_GENERAL);
 }
 </programlisting>
     </para>
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 485644f12d..c59ba4e5f8 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -53,6 +53,7 @@
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
+#include "storage/interrupt.h"
 #include "storage/lmgr.h"
 #include "utils/lsyscache.h"
 #include "utils/pg_rusage.h"
@@ -2607,11 +2608,11 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 6e4711dace..b12d8be647 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 set 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/parallel.c b/src/backend/access/transam/parallel.c
index 0a1e089ec1..27c3db9e3c 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -33,6 +33,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/spin.h"
@@ -755,16 +756,16 @@ 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.  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(1 << INTERRUPT_GENERAL,
+								   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_GENERAL);
 			}
 		}
 
@@ -873,15 +874,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * or latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_GENERAL should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1034,7 +1036,7 @@ HandleParallelMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 6f58412bca..5fe0bfc524 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,9 +84,9 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.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"
@@ -2676,7 +2676,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 		ProcNumber	walwriterProc = procglobal->walwriterProc;
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->walwriterProc);
 	}
 }
 
@@ -9347,11 +9347,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index b0c6d7c687..3e1d6074cf 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -28,7 +28,7 @@
 #include "pgstat.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -718,17 +718,17 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c6994b7828..676d6710ab 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -52,9 +52,10 @@
 #include "replication/slotsync.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -315,23 +316,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.
 	 */
@@ -466,7 +450,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -540,13 +523,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).
@@ -1634,13 +1610,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);
 }
 
 /*
@@ -1800,7 +1769,7 @@ PerformWalRecovery(void)
 			}
 
 			/*
-			 * If we've been asked to lag the primary, wait on latch until
+			 * If we've been asked to lag the primary, wait on interrupt until
 			 * enough time has passed.
 			 */
 			if (recoveryApplyDelay(xlogreader))
@@ -3023,8 +2992,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)
@@ -3032,11 +3001,19 @@ recoveryApplyDelay(XLogReaderState *record)
 
 	while (true)
 	{
-		ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+		/*
+		 * INTERRUPT_GENERAL is used for all the usual interrupts, like config
+		 * reloads.  The wakeups when more WAL arrive use a different
+		 * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL
+		 * arriving don't wake up the startup process excessively, when we're
+		 * waiting in other places, like for recovery conflicts.
+		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* This might change recovery_min_apply_delay. */
 		HandleStartupProcInterrupts();
 
+		ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 		if (CheckForStandbyTrigger())
 			break;
 
@@ -3057,10 +3034,11 @@ 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(1 << INTERRUPT_RECOVERY_CONTINUE |
+							 1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 msecs,
+							 WAIT_EVENT_RECOVERY_APPLY_DELAY);
 	}
 	return true;
 }
@@ -3703,15 +3681,17 @@ 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(1 << INTERRUPT_RECOVERY_CONTINUE |
+											 1 << INTERRUPT_GENERAL,
+											 WL_INTERRUPT | WL_TIMEOUT |
+											 WL_EXIT_ON_PM_DEATH,
+											 wait_time,
+											 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
+						ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 						now = GetCurrentTimestamp();
 
 						/* Handle interrupt signals of startup process */
+						ClearInterrupt(INTERRUPT_GENERAL);
 						HandleStartupProcInterrupts();
 					}
 					last_fail_time = now;
@@ -3978,11 +3958,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(1 << INTERRUPT_RECOVERY_CONTINUE |
+										 1 << INTERRUPT_GENERAL,
+										 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+										 -1L,
+										 WAIT_EVENT_RECOVERY_WAL_STREAM);
+					ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 					break;
 				}
 
@@ -4002,6 +3983,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 		 * This possibly-long loop needs to handle interrupts of startup
 		 * process.
 		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 		HandleStartupProcInterrupts();
 	}
 
@@ -4476,7 +4458,10 @@ CheckPromoteSignal(void)
 void
 WakeupRecovery(void)
 {
-	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
+
+	if (procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_RECOVERY_CONTINUE, procno);
 }
 
 /*
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index 5295b85fe0..41f0cde627 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -853,9 +853,9 @@ wal_segment_close(XLogReaderState *state)
  * output method outside walsender, e.g. in a bgworker.
  *
  * TODO: The walsender has its own version of this, but it relies on the
- * walsender's latch being set whenever WAL is flushed. No such infrastructure
- * exists for normal backends, so we have to do a check/sleep/repeat style of
- * loop for now.
+ * walsender's interrupt being set whenever WAL is flushed. No such
+ * infrastructure exists for normal backends, so we have to do a
+ * check/sleep/repeat style of loop for now.
  */
 int
 read_local_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr,
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index 4477945e61..3d865fa621 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -17,7 +17,7 @@
 #include "backup/basebackup_sink.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,21 +163,21 @@ throttle(bbsink_throttle *sink, size_t increment)
 		if (sleep <= 0)
 			break;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
-		/* We're eating a potentially set latch, so check for interrupts */
+		/* We're eating a wakeup, so check for interrupts */
 		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be
 		 * the maximum time to sleep. Thus the cast to long is safe.
 		 */
-		wait_result = WaitLatch(MyLatch,
-								WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								(long) (sleep / 1000),
-								WAIT_EVENT_BASE_BACKUP_THROTTLE);
+		wait_result = WaitInterrupt(1 << INTERRUPT_GENERAL,
+									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									(long) (sleep / 1000),
+									WAIT_EVENT_BASE_BACKUP_THROTTLE);
 
-		if (wait_result & WL_LATCH_SET)
+		if (wait_result & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
 
 		/* Done waiting? */
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 8ed503e1c1..bc954e9056 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -91,7 +91,7 @@
  *	  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
+ *	  raises INTERRUPT_GENERAL, which triggers the event to be processed
  *	  immediately if this backend 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
@@ -140,6 +140,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/procsignal.h"
@@ -406,9 +407,9 @@ static NotificationList *pendingNotifies = NULL;
 /*
  * 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.
+ * notifyInterruptPending flag and raises the INTERRUPT_GENERAL interrupt.
+ * ProcessNotifyInterrupt() will then be called whenever it's safe to actually
+ * deal with the interrupt.
  */
 volatile sig_atomic_t notifyInterruptPending = false;
 
@@ -1812,7 +1813,7 @@ HandleNotifyInterrupt(void)
 	notifyInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1821,10 +1822,10 @@ HandleNotifyInterrupt(void)
  *		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.
+ *		HandleNotifyInterrupt() will cause the read to be interrupted with
+ *		INTERRUPT_GENERAL, and this routine will get called.  If we are truly
+ *		idle (ie, *not* inside a transaction block), process the incoming
+ *		notifies.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index bb639ef51f..539e4660f1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2412,8 +2412,8 @@ vacuum_delay_point(void)
 		/*
 		 * 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 ca0f54d676..314e3d8fbb 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,8 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1028,7 +1027,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;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 7f7edc7f9f..453970e7da 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -36,6 +36,7 @@
 #include "executor/tqueue.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "storage/interrupt.h"
 #include "utils/wait_event.h"
 
 
@@ -382,9 +383,10 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 47e8c91606..ef7cba65e4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1663,8 +1663,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(hbaPort *port)
@@ -3098,8 +3099,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 5a009776d1..2382ea08a8 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -21,6 +21,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
+#include "storage/interrupt.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 
@@ -414,7 +415,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.
  *
@@ -450,9 +451,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
@@ -489,7 +489,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
@@ -668,9 +668,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 91a86d62a3..cdab6b0f59 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -32,7 +32,8 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -522,8 +523,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 2139f81f24..91346027d2 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -28,7 +28,7 @@
 #include <arpa/inet.h>
 
 #include "libpq/libpq.h"
-#include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -213,7 +213,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,12 +228,12 @@ 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,
@@ -241,9 +241,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientReadInterrupt(true);
 
 			/*
@@ -284,7 +284,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;
@@ -338,7 +339,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -350,9 +351,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientWriteInterrupt(true);
 
 			/*
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 896e1476b5..c4c32c450a 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -77,6 +77,7 @@
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
@@ -175,7 +176,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(sizeof(Port));
@@ -287,8 +288,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
@@ -306,18 +307,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,
+									  1 << INTERRUPT_GENERAL, 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;
 }
@@ -2060,7 +2061,7 @@ 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);
 
 retry:
 	rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0);
@@ -2068,15 +2069,15 @@ retry:
 	{
 		if (events[i].events & WL_SOCKET_CLOSED)
 			return false;
-		if (events[i].events & WL_LATCH_SET)
+		if (events[i].events & WL_INTERRUPT)
 		{
 			/*
-			 * A latch event might be preventing other events from being
+			 * An interrupt event might be preventing other events from being
 			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect latches to survive across
-			 * CHECK_FOR_INTERRUPTS().
+			 * because no code should expect INTERRUPT_GENERAL to survive
+			 * across CHECK_FOR_INTERRUPTS().
 			 */
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			goto retry;
 		}
 	}
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index fd735e2fea..6f6f6288e6 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -20,6 +20,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 
@@ -181,9 +182,10 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_GENERAL);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/libpq/pqsignal.c b/src/backend/libpq/pqsignal.c
index 22a16c50b2..2f5eebdfec 100644
--- a/src/backend/libpq/pqsignal.c
+++ b/src/backend/libpq/pqsignal.c
@@ -42,7 +42,7 @@ pqinitmask(void)
 {
 	sigemptyset(&UnBlockSig);
 
-	/* Note: InitializeLatchSupport() modifies UnBlockSig. */
+	/* Note: InitializeWaitEventSupport() modifies UnBlockSig. */
 
 	/* First set all signals, then clear some. */
 	sigfillset(&BlockSig);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index dc3cf87aba..aa5adce15a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -89,8 +89,8 @@
 #include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -571,10 +571,10 @@ AutoVacLauncherMain(char *startup_data, size_t startup_data_len)
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(!dlist_is_empty(&AutoVacuumShmem->av_freeWorkers),
@@ -582,14 +582,14 @@ AutoVacLauncherMain(char *startup_data, size_t startup_data_len)
 
 		/*
 		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * signal handlers will wake us by calling RaiseInterrupt).
 		 */
-		(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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		HandleAutoVacLauncherInterrupts();
 
@@ -1346,7 +1346,7 @@ static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index d19174bda3..5cc056cd5a 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -23,6 +23,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 07bc5517fc..828fc0b732 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,8 +21,8 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -1226,9 +1226,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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1236,7 +1236,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
@@ -1269,9 +1269,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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1279,7 +1279,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0f75548759..4200158a62 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -42,6 +42,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -224,7 +225,7 @@ BackgroundWriterMain(char *startup_data, size_t startup_data_len)
 		int			rc;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		HandleMainLoopInterrupts();
 
@@ -299,22 +300,22 @@ BackgroundWriterMain(char *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(1 << INTERRUPT_GENERAL,
+						   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
-		 * 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.
+		 * If no interrupt event 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
+		 * 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
@@ -329,10 +330,10 @@ BackgroundWriterMain(char *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(1 << INTERRUPT_GENERAL,
+								 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 982572a75d..97abec3138 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -49,6 +49,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
@@ -343,7 +344,7 @@ CheckpointerMain(char *startup_data, size_t startup_data_len)
 		bool		chkpt_or_rstpt_timed = false;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -555,10 +556,10 @@ CheckpointerMain(char *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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout * 1000L /* convert to ms */ ,
+							 WAIT_EVENT_CHECKPOINTER_MAIN);
 	}
 }
 
@@ -758,10 +759,11 @@ 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(1 << INTERRUPT_GENERAL,
+					  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
+					  100,
+					  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 	else if (--absorb_counter <= 0)
 	{
@@ -873,7 +875,7 @@ ReqCheckpointHandler(SIGNAL_ARGS)
 	 * The signaling process should have set ckpt_flags nonzero, so all we
 	 * need do is ensure that our main loop gets kicked out of any wait.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
@@ -1145,7 +1147,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->checkpointerProc);
 	}
 
 	return true;
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index eedc0980cf..af3443efc3 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -18,8 +18,8 @@
 
 #include "miscadmin.h"
 #include "postmaster/interrupt.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -61,7 +61,7 @@ void
 SignalHandlerForConfigReload(SIGNAL_ARGS)
 {
 	ConfigReloadPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -105,5 +105,5 @@ void
 SignalHandlerForShutdownRequest(SIGNAL_ARGS)
 {
 	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 02f91431f5..b32cf788ea 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -41,8 +41,8 @@
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -247,8 +247,8 @@ PgArchiverMain(char *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;
 
@@ -282,13 +282,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(&ProcGlobal->allProcs[arch_pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, arch_pgprocno);
 }
 
 
@@ -298,7 +297,7 @@ pgarch_waken_stop(SIGNAL_ARGS)
 {
 	/* set flag to do a final cycle and shut down afterwards */
 	ready_to_stop = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -318,7 +317,7 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
 		time_to_stop = ready_to_stop;
@@ -355,10 +354,10 @@ 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(1 << INTERRUPT_GENERAL,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 6376d43087..4e28bfb2f9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -109,6 +109,7 @@
 #include "replication/slotsync.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "tcop/backend_startup.h"
@@ -535,8 +536,7 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGCHLD, handle_pm_child_exit_signal);
 
 	/* This may configure SIGURG, depending on platform. */
-	InitializeLatchSupport();
-	InitProcessLocalLatch();
+	InitializeWaitEventSupport();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -1595,14 +1595,14 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
-					  NULL);
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  1 << INTERRUPT_GENERAL, NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1631,19 +1631,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_GENERAL);
 
 			/*
 			 * 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)
@@ -1937,7 +1938,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1947,7 +1948,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2024,7 +2025,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2185,7 +2186,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index ef6f98ebcd..2020f170d0 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -28,6 +28,7 @@
 #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"
@@ -40,7 +41,7 @@
  * 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 HandleStartupProcInterrupts().
  * 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
@@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
 	/* Shutdown the recovery environment */
 	if (standbyState != STANDBY_DISABLED)
 		ShutdownRecoveryTransactionEnvironment();
+
+	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
 }
 
 
@@ -220,6 +223,12 @@ StartupProcessMain(char *startup_data, size_t startup_data_len)
 	MyBackendType = B_STARTUP;
 	AuxiliaryProcessMainCommon();
 
+	/*
+	 * Advertise our proc number so that backends can wake us up, when the
+	 * server is promoted or recovery is paused/resumed.
+	 */
+	ProcGlobal->startupProc = MyProcNumber;
+
 	/* Arrange to clean up at startup process exit */
 	on_shmem_exit(StartupProcExit, 0);
 
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index f12639056f..7da4916567 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -44,8 +44,8 @@
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -328,7 +328,7 @@ SysLoggerMain(char *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
@@ -338,9 +338,9 @@ SysLoggerMain(char *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, 1 << INTERRUPT_GENERAL, 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 */
@@ -356,7 +356,7 @@ SysLoggerMain(char *startup_data, size_t startup_data_len)
 #endif
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -1186,7 +1186,7 @@ pipeThread(void *arg)
 			if (ftell(syslogFile) >= Log_RotationSize * 1024L ||
 				(csvlogFile != NULL && ftell(csvlogFile) >= Log_RotationSize * 1024L) ||
 				(jsonlogFile != NULL && ftell(jsonlogFile) >= Log_RotationSize * 1024L))
-				SetLatch(MyLatch);
+				RaiseInterrupt(INTERRUPT_GENERAL);
 		}
 		LeaveCriticalSection(&sysloggerSection);
 	}
@@ -1197,8 +1197,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_GENERAL);
 
 	LeaveCriticalSection(&sysloggerSection);
 	_endthread();
@@ -1593,5 +1593,5 @@ static void
 sigUsr1Handler(SIGNAL_ARGS)
 {
 	rotation_requested = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 48350bec52..ad646fb3c0 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -38,8 +38,8 @@
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -315,10 +315,8 @@ WalSummarizerMain(char *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) */
@@ -630,8 +628,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)
@@ -646,7 +644,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&ProcGlobal->allProcs[pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, pgprocno);
 }
 
 /*
@@ -1637,11 +1635,11 @@ summarizer_wait_for_wal(void)
 	}
 
 	/* 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(1 << INTERRUPT_GENERAL,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_GENERAL);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 5a3cb89465..18073590e9 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -54,6 +54,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -222,12 +223,12 @@ WalWriterMain(char *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))
 		{
@@ -236,7 +237,7 @@ WalWriterMain(char *startup_data, size_t startup_data_len)
 		}
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* Process any signals received recently */
 		HandleMainLoopInterrupts();
@@ -263,9 +264,9 @@ WalWriterMain(char *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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index c74369953f..f8bd2b67d6 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -30,7 +30,7 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
@@ -237,16 +237,16 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical,
 		else
 			io_flag = WL_SOCKET_WRITEABLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-							   PQsocket(conn->streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
@@ -848,17 +848,17 @@ libpqrcv_PQgetResult(PGconn *streamConn)
 		 * since we'll get interrupted by signals and can handle any
 		 * interrupts here.
 		 */
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-							   WL_LATCH_SET,
-							   PQsocket(streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+								   WL_INTERRUPT,
+								   PQsocket(streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index e7f7d4c5e4..eb5dc3116e 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -165,6 +165,7 @@
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "tcop/tcopprot.h"
@@ -804,13 +805,13 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 				int			rc;
 
 				/* Wait for more work. */
-				rc = WaitLatch(MyLatch,
-							   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   1000L,
-							   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
+				rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+								   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   1000L,
+								   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
 
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				if (rc & WL_INTERRUPT)
+					ClearInterrupt(INTERRUPT_GENERAL);
 			}
 		}
 		else
@@ -990,7 +991,7 @@ HandleParallelApplyMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1182,14 +1183,14 @@ 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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   SHM_SEND_RETRY_INTERVAL_MS,
+						   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1254,13 +1255,13 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo,
 			break;
 
 		/* Wait to be signalled. */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 10L,
-						 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L,
+							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* 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 e5fdca8bbf..9765a099ec 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -34,6 +34,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -218,16 +219,17 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
@@ -553,13 +555,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -594,13 +596,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -676,7 +678,7 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 }
 
 /*
- * Wake up (using latch) any logical replication worker for specified sub/rel.
+ * Wake up (using interrupt) any logical replication worker for specified sub/rel.
  */
 void
 logicalrep_worker_wakeup(Oid subid, Oid relid)
@@ -694,7 +696,7 @@ logicalrep_worker_wakeup(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.
  */
@@ -703,7 +705,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -1221,14 +1223,14 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   wait_time,
-					   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   wait_time,
+						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index f4f80b2312..9b5f5bb158 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,6 +59,7 @@
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -1252,13 +1253,13 @@ 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);
+	rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1590,13 +1591,13 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 7c8a0e9cfe..dff4f5cbc4 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -111,6 +111,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -210,11 +211,11 @@ wait_for_relation_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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return false;
@@ -260,15 +261,15 @@ 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(1 << INTERRUPT_GENERAL,
+						   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_GENERAL);
 	}
 
 	return false;
@@ -555,7 +556,7 @@ process_syncing_tables_for_apply(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
 						 */
 						CommitTransactionCommand();
 						pgstat_report_stat(false);
@@ -771,14 +772,14 @@ 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);
+		(void) WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+									 WL_SOCKET_READABLE | WL_INTERRUPT |
+									 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return bytesread;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 925dff9cc4..74037c8839 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -177,6 +177,7 @@
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/buffile.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "tcop/tcopprot.h"
@@ -3729,26 +3730,26 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
 		else
 			wait_time = NAPTIME_PER_CYCLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index e1126734ef..8b6b1e8fc1 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -81,6 +81,7 @@
 #include "replication/syncrep.h"
 #include "replication/walsender.h"
 #include "replication/walsender_private.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc_hooks.h"
@@ -222,22 +223,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -282,11 +283,13 @@ 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 interrupt, so no need for timeout.
 		 */
-		rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1,
-					   WAIT_EVENT_SYNC_REP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH,
+						   -1,
+						   WAIT_EVENT_SYNC_REP);
 
 		/*
 		 * If the postmaster dies, we'll probably never get an acknowledgment,
@@ -902,7 +905,7 @@ SyncRepWakeQueue(bool all, int mode)
 		/*
 		 * Wake only when we have set state and removed from queue.
 		 */
-		SetLatch(&(proc->procLatch));
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 		numprocs++;
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 5f641d2790..3b7cd6290d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -67,6 +67,7 @@
 #include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -146,17 +147,17 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
 static void WalRcvComputeNextWakeup(WalRcvWakeupReason reason, TimestampTz now);
 
 /*
- * Process any interrupts the walreceiver process may have received.
- * This should be called any time the process's latch has become set.
+ * Process any interrupts the walreceiver process may have received.  This
+ * should be called any time the INTERRUPT_GENERAL interrupt has become set.
  *
  * Currently, only SIGTERM is of interest.  We can't just exit(1) within the
  * SIGTERM signal handler, because the signal might arrive in the middle of
  * some critical operation, like while we're holding a spinlock.  Instead, the
- * signal handler sets a flag variable as well as setting the process's latch.
+ * signal handler sets a flag variable as well as raising INTERRUPT_GENERAL.
  * We must check the flag (by calling ProcessWalRcvInterrupts) anytime the
- * latch has become set.  Operations that could block for a long time, such as
- * reading from a remote server, must pay attention to the latch too; see
- * libpqrcv_PQgetResult for example.
+ * INTERRUPT_GENERAL has become set.  Operations that could block for a long
+ * time, such as reading from a remote server, must pay attention to the
+ * interrupt too; see libpqrcv_PQgetResult for example.
  */
 void
 ProcessWalRcvInterrupts(void)
@@ -536,25 +537,25 @@ WalReceiverMain(char *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(1 << INTERRUPT_GENERAL,
+										   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_GENERAL);
 					ProcessWalRcvInterrupts();
 
 					if (walrcv->force_reply)
@@ -692,7 +693,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessWalRcvInterrupts();
 
@@ -724,8 +725,10 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
 	if (update_process_title)
@@ -1366,7 +1369,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 8557d10cf9..cbe73b9dc7 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -26,6 +26,7 @@
 #include "access/xlogrecovery.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
+#include "storage/interrupt.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -317,7 +318,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_GENERAL, walrcv_proc);
 }
 
 /*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 371eef3ddd..ad8929050d 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -80,6 +80,7 @@
 #include "replication/walsender_private.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -1013,8 +1014,8 @@ 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
+ * Inside the walsender we can do better than read_local_xlog_page, which
+ * has to do a plain sleep/busy loop, because the walsender's interrupt gets
  * set every time WAL is flushed.
  */
 static int
@@ -1611,7 +1612,7 @@ ProcessPendingWrites(void)
 				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1628,8 +1629,8 @@ ProcessPendingWrites(void)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1817,7 +1818,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		long		sleeptime;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1937,8 +1938,8 @@ WalSndWaitForWal(XLogRecPtr loc)
 		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	return RecentFlushPtr;
 }
 
@@ -2755,7 +2756,7 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	for (;;)
 	{
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -3554,7 +3555,7 @@ static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* Set up signal handlers */
@@ -3660,7 +3661,7 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
@@ -3672,8 +3673,8 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 * 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().
+	 * 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
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index cc9782b713..1686331b11 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -51,6 +51,7 @@
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -2882,7 +2883,7 @@ UnpinBufferNoOwner(BufferDesc *buf)
 
 				buf_state &= ~BM_PIN_COUNT_WAITER;
 				UnlockBufHdr(buf, buf_state);
-				ProcSendSignal(wait_backend_pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, wait_backend_pgprocno);
 			}
 			else
 				UnlockBufHdr(buf, buf_state);
@@ -5344,7 +5345,12 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+		{
+			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_PIN);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index dffdd57e9b..65b02f241c 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -19,6 +19,7 @@
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 
 #define INT_ACCESS_ONCE(var)	((int)(*((volatile int *)&(var))))
@@ -219,27 +220,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *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(&ProcGlobal->allProcs[bgwprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, bgwprocno);
 	}
 
 	/*
@@ -420,10 +419,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 d8a1653eb6..5c7c72f902 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -13,9 +13,9 @@ OBJS = \
 	dsm.o \
 	dsm_impl.o \
 	dsm_registry.o \
+	interrupt.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
@@ -25,6 +25,7 @@ OBJS = \
 	signalfuncs.o \
 	sinval.o \
 	sinvaladt.o \
-	standby.o
+	standby.o \
+	waiteventset.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/interrupt.c b/src/backend/storage/ipc/interrupt.c
new file mode 100644
index 0000000000..53473a352d
--- /dev/null
+++ b/src/backend/storage/ipc/interrupt.c
@@ -0,0 +1,125 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Interrupt handling routines.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "storage/interrupt.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procsignal.h"
+#include "storage/waiteventset.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+
+static pg_atomic_uint32 LocalPendingInterrupts;
+
+pg_atomic_uint32 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch backx.
+	 */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ */
+void
+RaiseInterrupt(InterruptType reason)
+{
+	uint32		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << reason);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ */
+void
+SendInterrupt(InterruptType reason, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint32		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, 1 << reason);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 5a936171f7..4eba41b78a 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -5,9 +5,9 @@ backend_sources += files(
   'dsm.c',
   'dsm_impl.c',
   'dsm_registry.c',
+  'interrupt.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
@@ -18,5 +18,6 @@ backend_sources += files(
   'sinval.c',
   'sinvaladt.c',
   'standby.c',
+  'waiteventset.c',
 
 )
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 87027f27eb..2efba9ee05 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -25,8 +25,8 @@
 #include "replication/logicalworker.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -481,7 +481,7 @@ HandleProcSignalBarrierInterrupt(void)
 {
 	InterruptPending = true;
 	ProcSignalBarrierPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* interrupt will be raised by procsignal_sigusr1_handler */
 }
 
 /*
@@ -712,7 +712,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
 		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
 
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 9235fcd08e..de0a5221e3 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.
  *
@@ -22,6 +22,7 @@
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/bgworker.h"
+#include "storage/interrupt.h"
 #include "storage/shm_mq.h"
 #include "storage/spin.h"
 #include "utils/memutils.h"
@@ -44,10 +45,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 SendIntterupt/ClearInterrupt, do we need to add some?
  *
  * mq_ring_size and mq_ring_offset never change after initialization, and
  * can therefore be read without the lock.
@@ -112,7 +114,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
@@ -214,7 +216,7 @@ shm_mq_set_receiver(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (sender != NULL)
-		SetLatch(&sender->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
@@ -232,7 +234,7 @@ shm_mq_set_sender(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (receiver != NULL)
-		SetLatch(&receiver->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(receiver));
 }
 
 /*
@@ -341,14 +343,14 @@ 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 on our process interrupt when the ring
+ * buffer fills up, and then continue writing once the receiver has drained
+ * some data.  The process interrupt is cleared after each wait.
  *
- * When nowait = true, we do not manipulate the state of the process latch;
+ * When nowait = true, we do not manipulate the state of the process interrupt;
  * instead, if the buffer becomes full, we return SHM_MQ_WOULD_BLOCK.  In
  * this case, the caller should call this function again, with the same
- * arguments, each time the process latch is set.  (Once begun, the sending
+ * arguments, each time the process interrupt is set.  (Once begun, the sending
  * of a message cannot be aborted except by detaching from the queue; changing
  * the length or payload will corrupt the queue.)
  *
@@ -539,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_GENERAL, GetNumberFromPGProc(receiver));
 		mqh->mqh_send_pending = 0;
 	}
 
@@ -557,16 +559,16 @@ 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
+ * When nowait = false, we'll wait on our process interrupt when the ring
+ * buffer is empty and we have not yet received a full message.  The sender
+ * will set our process interrupt after more data has been written, and we'll
+ * resume processing.  Each call will therefore return a complete message
  * (unless the sender detaches the queue).
  *
- * When nowait = 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 manipulate the state of the process
+ * interrupt; instead, whenever the buffer is empty and we need to read from
+ * it, we return SHM_MQ_WOULD_BLOCK.  In this case, the caller should call
+ * this function again after the process interrupt has been set.
  */
 shm_mq_result
 shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
@@ -619,8 +621,8 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
 	 * If we've consumed an amount of data greater than 1/4th of the ring
 	 * size, mark it consumed in shared memory.  We try to avoid doing this
 	 * unnecessarily when only a small amount of data has been consumed,
-	 * because SetLatch() is fairly expensive and we don't want to do it too
-	 * often.
+	 * because SendInterrupt() is fairly expensive and we don't want to do it
+	 * too often.
 	 */
 	if (mqh->mqh_consume_pending > mq->mq_ring_size / 4)
 	{
@@ -895,7 +897,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +995,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 * Therefore, we can read it without acquiring the spinlock.
 			 */
 			Assert(mqh->mqh_counterparty_attached);
-			SetLatch(&mq->mq_receiver->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(mq->mq_receiver));
 
 			/*
 			 * We have just updated the mqh_send_pending bytes in the shared
@@ -1001,7 +1003,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 */
 			mqh->mqh_send_pending = 0;
 
-			/* Skip manipulation of our latch if nowait = true. */
+			/* Skip waiting if nowait = true. */
 			if (nowait)
 			{
 				*bytes_written = sent;
@@ -1009,17 +1011,17 @@ 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(1 << INTERRUPT_GENERAL, 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_GENERAL);
 
 			/* An interrupt may have occurred while we were waiting. */
 			CHECK_FOR_INTERRUPTS();
@@ -1054,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.
 			 */
@@ -1150,22 +1152,22 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1250,11 +1252,11 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1293,7 +1295,7 @@ shm_mq_inc_bytes_read(shm_mq *mq, Size n)
 	 */
 	sender = mq->mq_sender;
 	Assert(sender != NULL);
-	SetLatch(&sender->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index d835327600..2ac9f09f7a 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -20,6 +20,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
+#include "storage/interrupt.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -204,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index d9b16f84d1..72630b27aa 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -16,7 +16,7 @@
 
 #include "access/xact.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
@@ -31,8 +31,8 @@ uint64		SharedInvalidMessageCounter;
  * 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
+ * The signal handler will set an interrupt pending flag and raise the
+ * INTERRUPT_GENERAL. Whenever starting to read from the client, or when
  * interrupted while doing so, ProcessClientReadInterrupt() will call
  * ProcessCatchupEvent().
  */
@@ -148,7 +148,7 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
  *
  * 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.
+ * raising INTERRUPT_GENERAL.
  */
 void
 HandleCatchupInterrupt(void)
@@ -161,7 +161,7 @@ HandleCatchupInterrupt(void)
 	catchupInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 25267f0f85..73de96acc7 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -26,6 +26,7 @@
 #include "pgstat.h"
 #include "replication/slot.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/sinvaladt.h"
@@ -594,7 +595,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
@@ -696,7 +697,10 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  PG_WAIT_LOCK | locktag.locktag_type);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	/*
 	 * Exit if ltime is reached. Then all the backends holding conflicting
@@ -745,7 +749,10 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+					  PG_WAIT_LOCK | locktag.locktag_type);
+		ClearInterrupt(INTERRUPT_GENERAL);
+		CHECK_FOR_INTERRUPTS();
 	}
 
 cleanup:
@@ -837,9 +844,14 @@ 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
+	 * the signal handler??
 	 */
-	ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+	WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  WAIT_EVENT_BUFFER_PIN);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	if (got_standby_delay_timeout)
 		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/waiteventset.c
similarity index 76%
rename from src/backend/storage/ipc/latch.c
rename to src/backend/storage/ipc/waiteventset.c
index f19ce5e7af..ed3758cd4a 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -1,17 +1,32 @@
 /*-------------------------------------------------------------------------
  *
- * latch.c
- *	  Routines for inter-process latches
+ * waiteventset.c
+ *	  ppoll()/pselect() like abstraction
  *
- * 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.
+ * WaitEvents are an abstraction for waiting for one or more events at a time.
+ * The waiting can be done in a race free fashion, similar ppoll() or
+ * pselect() (as opposed to plain poll()/select()).
+ *
+ * You can wait for:
+ * - 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)
+ *
+ * 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 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
@@ -27,7 +42,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
+ *	  src/backend/storage/ipc/waiteventset.c
  *
  *-------------------------------------------------------------------------
  */
@@ -57,9 +72,11 @@
 #include "portability/instr_time.h"
 #include "postmaster/postmaster.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
+#include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
 
@@ -98,7 +115,7 @@
 #endif
 #endif
 
-/* typedef in latch.h */
+/* typedef in waiteventset.h */
 struct WaitEventSet
 {
 	ResourceOwner owner;
@@ -113,13 +130,13 @@ struct WaitEventSet
 	WaitEvent  *events;
 
 	/*
-	 * If WL_LATCH_SET is specified in any wait event, latch is a pointer to
-	 * said latch, and latch_pos the offset in the ->events array. This is
-	 * useful because we check the state of the latch before performing doing
-	 * syscalls related to waiting.
+	 * If WL_INTERRUPT is specified in any wait event, interruptMask is the
+	 * interrupts to wait for, and interrupt_pos the offset in the ->events
+	 * array. This is useful because we check the state of pending interrupts
+	 * before performing doing syscalls related to waiting.
 	 */
-	Latch	   *latch;
-	int			latch_pos;
+	uint32		interrupt_mask;
+	int			interrupt_pos;
 
 	/*
 	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
@@ -151,14 +168,14 @@ struct WaitEventSet
 #endif
 };
 
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
 
-/* The position of the latch in LatchWaitSet. */
-#define LatchWaitSetLatchPos 0
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently in WaitInterrupt? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -174,9 +191,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
 
@@ -223,13 +247,13 @@ ResourceOwnerForgetWaitEventSet(ResourceOwner owner, WaitEventSet *set)
 
 
 /*
- * Initialize the process-local latch infrastructure.
+ * 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
-InitializeLatchSupport(void)
+InitializeWaitEventSupport(void)
 {
 #if defined(WAIT_USE_SELF_PIPE)
 	int			pipefd[2];
@@ -276,12 +300,12 @@ InitializeLatchSupport(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");
@@ -302,7 +326,7 @@ InitializeLatchSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -340,37 +364,43 @@ InitializeLatchSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 void
-InitializeLatchWaitSet(void)
+InitializeInterruptWaitSet(void)
 {
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
 
-	Assert(LatchWaitSet == NULL);
+	Assert(InterruptWaitSet == NULL);
 
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, 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(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-						  PGINVALID_SOCKET, NULL, NULL);
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
 
-	Assert(latch_pos == LatchWaitSetLatchPos);
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
 }
 
 void
-ShutdownLatchSupport(void)
+ShutdownWaitEventSupport(void)
 {
 #if defined(WAIT_USE_POLL)
 	pqsignal(SIGURG, SIG_IGN);
 #endif
 
-	if (LatchWaitSet)
+	if (InterruptWaitSet)
 	{
-		FreeWaitEventSet(LatchWaitSet);
-		LatchWaitSet = NULL;
+		FreeWaitEventSet(InterruptWaitSet);
+		InterruptWaitSet = NULL;
 	}
 
 #if defined(WAIT_USE_SELF_PIPE)
@@ -388,134 +418,24 @@ ShutdownLatchSupport(void)
 }
 
 /*
- * 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;
-
-#if defined(WAIT_USE_SELF_PIPE)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
-#elif defined(WAIT_USE_SIGNALFD)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(signal_fd >= 0);
-#elif defined(WAIT_USE_WIN32)
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-#if defined(WAIT_USE_SELF_PIPE)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
-#elif defined(WAIT_USE_SIGNALFD)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(signal_fd >= 0);
-#endif
-
-	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.
+ * 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.
  *
- * 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)
+WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
 {
 	WaitEvent	event;
 
@@ -525,17 +445,17 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
 		   (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.
+	 * 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_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-	LatchWaitSet->exit_on_postmaster_death =
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
+	InterruptWaitSet->exit_on_postmaster_death =
 		((wakeEvents & WL_EXIT_ON_PM_DEATH) != 0);
 
-	if (WaitEventSetWait(LatchWaitSet,
+	if (WaitEventSetWait(InterruptWaitSet,
 						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
 						 &event, 1,
 						 wait_event_info) == 0)
@@ -545,7 +465,7 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
 }
 
 /*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
+ * 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
@@ -558,12 +478,12 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
  * 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
+ * using an interrupt 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)
+WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
 {
 	int			ret = 0;
 	int			rc;
@@ -575,9 +495,9 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
 	else
 		timeout = -1;
 
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
 
 	/* Postmaster-managed callers must handle postmaster death somehow. */
 	Assert(!IsUnderPostmaster ||
@@ -586,18 +506,18 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
 
 	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
 		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
+						  0, NULL);
 
 	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
 		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
+						  0, NULL);
 
 	if (wakeEvents & WL_SOCKET_MASK)
 	{
 		int			ev;
 
 		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
 	}
 
 	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
@@ -606,7 +526,7 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
 		ret |= WL_TIMEOUT;
 	else
 	{
-		ret |= event.events & (WL_LATCH_SET |
+		ret |= event.events & (WL_INTERRUPT |
 							   WL_POSTMASTER_DEATH |
 							   WL_SOCKET_MASK);
 	}
@@ -617,125 +537,47 @@ WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
 }
 
 /*
- * Sets a latch and wakes up anyone waiting on it.
+ * Wake up my process if it's currently waiting.
  *
- * 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: 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.) XXX
  *
  * NB: this function is called from critical sections and signal handlers so
  * throwing an error is not a good idea.
  */
 void
-SetLatch(Latch *latch)
+WakeupMyProc(void)
 {
 #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)
-	{
 #if defined(WAIT_USE_SELF_PIPE)
-		if (waiting)
-			sendSelfPipeByte();
+	if (waiting)
+		sendSelfPipeByte();
 #else
-		if (waiting)
-			kill(MyProcPid, SIGURG);
+	if (waiting)
+		kill(MyProcPid, SIGURG);
 #endif
-	}
-	else
-		kill(owner_pid, SIGURG);
-
 #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().
-		 */
-	}
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
 #endif
 }
 
 /*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
+ * Wake up another process if it's currently waiting.
  */
 void
-ResetLatch(Latch *latch)
+WakeupOtherProc(PGPROC *proc)
 {
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
 
 	/*
-	 * 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.
+	 * 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().
 	 */
-	pg_memory_barrier();
+#endif
 }
 
 /*
@@ -799,7 +641,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;
 
@@ -884,9 +727,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)
 		{
@@ -923,7 +766,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 set
  * - 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
@@ -941,10 +784,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.
@@ -954,7 +793,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, uint32 interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -968,19 +807,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 */
@@ -996,10 +832,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)
@@ -1033,14 +869,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, uint32 interruptMask)
 {
 	WaitEvent  *event;
 #if defined(WAIT_USE_KQUEUE)
@@ -1055,19 +891,19 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 #endif
 
 	/*
-	 * 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 &&
+	if (event->events & WL_INTERRUPT &&
 		events != event->events)
 	{
-		elog(ERROR, "cannot modify latch event");
+		elog(ERROR, "cannot modify interrupts event");
 	}
 
 	if (event->events & WL_POSTMASTER_DEATH)
@@ -1078,25 +914,19 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -1126,9 +956,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)
@@ -1175,9 +1004,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)
@@ -1239,9 +1067,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;
@@ -1267,8 +1095,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 |
@@ -1283,10 +1110,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
 	{
@@ -1364,10 +1191,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)
 	{
@@ -1423,6 +1253,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);
 
@@ -1444,63 +1275,77 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether. We don't attempt to report any other events that
+	 * might also be satisfied in that case.
+	 *
+	 * 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.
+	 *
+	 * 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.".
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		uint32		old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already. If so, leave the loop
-		 * immediately, avoid blocking again. We don't attempt to report any
-		 * other events that might also be satisfied.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			break;
+			/* we will fall through without sleeping */
 		}
+	}
+
+	while (returned_events == 0)
+	{
+		int			rc;
 
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
@@ -1510,12 +1355,6 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 		rc = WaitEventSetWaitBlock(set, cur_timeout,
 								   occurred_events, nevents);
 
-		if (set->latch)
-		{
-			Assert(set->latch->maybe_sleeping);
-			set->latch->maybe_sleeping = false;
-		}
-
 		if (rc == -1)
 			break;				/* timeout occurred */
 		else
@@ -1531,6 +1370,11 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 				break;
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) 1 << SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1601,16 +1445,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->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1763,13 +1607,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->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1885,16 +1729,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->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1993,6 +1837,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
@@ -2098,19 +1951,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -2155,7 +2004,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
@@ -2261,18 +2110,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)
 {
@@ -2289,7 +2137,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;
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 112a518bae..59fc6466b9 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -21,6 +21,7 @@
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/proclist.h"
 #include "storage/spin.h"
@@ -147,23 +148,23 @@ 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(1 << INTERRUPT_GENERAL, 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_GENERAL);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -176,9 +177,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))
@@ -266,9 +268,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_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -297,8 +299,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
@@ -331,7 +333,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -355,6 +357,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 2030322f95..a84071056a 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -207,6 +207,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
+#include "storage/interrupt.h"
 #include "storage/predicate.h"
 #include "storage/predicate_internals.h"
 #include "storage/proc.h"
@@ -1576,7 +1577,10 @@ GetSafeSnapshot(Snapshot origSnapshot)
 				 SxactIsROUnsafe(MySerializableXact)))
 		{
 			LWLockRelease(SerializableXactHashLock);
-			ProcWaitForSignal(WAIT_EVENT_SAFE_SNAPSHOT);
+			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_SAFE_SNAPSHOT);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
 			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 		}
 		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
@@ -3609,7 +3613,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe)
 			 */
 			if (SxactIsDeferrableWaiting(roXact) &&
 				(SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact)))
-				ProcSendSignal(roXact->pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, roXact->pgprocno);
 		}
 	}
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 720ef99ee8..8d1d31a279 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -42,6 +42,7 @@
 #include "replication/slotsync.h"
 #include "replication/syncrep.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -257,14 +258,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -474,13 +489,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);
@@ -644,13 +654,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -927,21 +932,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1004,13 +1008,12 @@ AuxiliaryProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/* look at the equivalent ProcKill() code for comments */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	SpinLockAcquire(ProcStructLock);
 
@@ -1337,18 +1340,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
 	{
@@ -1394,9 +1397,9 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
@@ -1688,7 +1691,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 links invalid.
  *
@@ -1717,7 +1720,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1869,45 +1872,19 @@ 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.
+	 * 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.
 	 *
 	 * Note that, when this function runs inside procsignal_sigusr1_handler(),
-	 * the handler function sets the latch again after the latch is set here.
+	 * the handler function raises the interrupt again after the interrupt is
+	 * raised here.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	errno = save_errno;
 }
 
-/*
- * 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(&ProcGlobal->allProcs[procNumber].procLatch);
-}
-
 /*
  * BecomeLockGroupLeader - designate process as lock group leader
  *
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index ab7137d0ff..bec42f4060 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -27,7 +27,7 @@
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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/postgres.c b/src/backend/tcop/postgres.c
index 4b985bd056..3cb554fe5e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -61,6 +61,7 @@
 #include "replication/walsender.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -531,15 +532,15 @@ ProcessClientReadInterrupt(bool blocked)
 		/*
 		 * 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
+		 * make sure the interrupt flag is set, so that if there is no data
 		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while reading.
 		 */
 		if (blocked)
 			CHECK_FOR_INTERRUPTS();
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -565,9 +566,9 @@ ProcessClientWriteInterrupt(bool blocked)
 		 * 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
+		 * sure the interrupt flag is set, so that if the write would block
 		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while writing.
 		 */
 		if (blocked)
@@ -591,7 +592,7 @@ ProcessClientWriteInterrupt(bool blocked)
 			}
 		}
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -3024,12 +3025,12 @@ 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);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on 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.
 	 */
@@ -3053,8 +3054,8 @@ StatementCancelHandler(SIGNAL_ARGS)
 		QueryCancelPending = true;
 	}
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* signal handler for floating point exception */
@@ -3080,7 +3081,7 @@ HandleRecoveryConflictInterrupt(ProcSignalReason reason)
 	RecoveryConflictPendingReasons[reason] = true;
 	RecoveryConflictPending = true;
 	InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 1aa8bbb306..5482df1438 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -38,7 +38,7 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -373,16 +373,16 @@ pg_sleep(PG_FUNCTION_ARGS)
 	float8		endtime;
 
 	/*
-	 * 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.
 	 */
 #define GetNowFloat()	((float8) GetCurrentTimestamp() / 1000000.0)
 
@@ -403,11 +403,11 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 delay_ms,
+							 WAIT_EVENT_PG_SLEEP);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index e00cd3c969..f3e3cafc09 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1738,10 +1738,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/init/globals.c b/src/backend/utils/init/globals.c
index 03a54451ac..cb5343d28c 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -52,15 +52,6 @@ bool		MyCancelKeyValid = false;
 int32		MyCancelKey = 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 3b7b2ebec0..b507f4c3dc 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -42,8 +42,8 @@
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,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
  *
@@ -133,10 +131,9 @@ InitPostmasterChild(void)
 	pqinitmask();
 #endif
 
-	/* Initialize process-local latch support */
-	InitializeLatchSupport();
-	InitProcessLocalLatch();
-	InitializeLatchWaitSet();
+	/* Initialize process-local interrupt support */
+	InitializeWaitEventSupport();
+	InitializeInterruptWaitSet();
 
 	/*
 	 * If possible, make this process a group leader, so that the postmaster
@@ -194,10 +191,9 @@ InitStandaloneProcess(const char *argv0)
 
 	InitProcessGlobals();
 
-	/* Initialize process-local latch support */
-	InitializeLatchSupport();
-	InitProcessLocalLatch();
-	InitializeLatchWaitSet();
+	/* Initialize process-local interrupt support */
+	InitializeWaitEventSupport();
+	InitializeInterruptWaitSet();
 
 	/*
 	 * For consistency with InitPostmasterChild, initialize signal mask here.
@@ -218,48 +214,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 5b657a3f13..cc96f75900 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -46,6 +46,7 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -1388,7 +1389,7 @@ TransactionTimeoutHandler(void)
 {
 	TransactionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1396,7 +1397,7 @@ IdleInTransactionSessionTimeoutHandler(void)
 {
 	IdleInTransactionSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1404,7 +1405,7 @@ IdleSessionTimeoutHandler(void)
 {
 	IdleSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1412,7 +1413,7 @@ IdleStatsUpdateTimeoutHandler(void)
 {
 	IdleStatsUpdateTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1420,7 +1421,7 @@ ClientCheckTimeoutHandler(void)
 {
 	CheckClientConnectionPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ec7e570920..44b8ceabee 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,7 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -371,10 +371,10 @@ handle_sig_alarm(SIGNAL_ARGS)
 	HOLD_INTERRUPTS();
 
 	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
+	 * SIGALRM is always cause for waking anything waiting on
+	 * INTERRUPT_GENERAL.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 70d33226cb..065ec37da6 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1273,7 +1273,7 @@ HandleLogMemoryContextInterrupt(void)
 {
 	InterruptPending = true;
 	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index 69ffe5498f..7daec7ef83 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -14,6 +14,8 @@
 #ifndef PARALLEL_H
 #define PARALLEL_H
 
+#include <signal.h>
+
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 46a90dfb02..b8819cec69 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -43,7 +43,7 @@
 #include "libpq-fe.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -177,8 +177,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -209,16 +209,16 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
+			if (rc & WL_INTERRUPT)
 			{
-				ResetLatch(MyLatch);
+				ClearInterrupt(INTERRUPT_GENERAL);
 				CHECK_FOR_INTERRUPTS();
 			}
 
@@ -341,17 +341,17 @@ 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(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -407,7 +407,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)
@@ -436,10 +436,11 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
+			WaitInterruptOrSocket(1 << INTERRUPT_GENERAL, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 07e5b12536..433fba9bd9 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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/miscadmin.h b/src/include/miscadmin.h
index 42a2b38cac..3d442f256d 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -190,7 +190,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT bool MyCancelKeyValid;
 extern PGDLLIMPORT int32 MyCancelKey;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -317,9 +316,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/storage/interrupt.h b/src/include/storage/interrupt.h
new file mode 100644
index 0000000000..1da05369c3
--- /dev/null
+++ b/src/include/storage/interrupt.h
@@ -0,0 +1,171 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Interrupt handling routines.
+ *
+ * "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.
+ *
+ * Interrupt flags can be "raised" synchronously by code that wants to defer
+ * an action, or asynchronously by timer signal handlers, other signal
+ * handlers or "sent" by other backends setting them directly.
+ *
+ * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is
+ * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query
+ * cancellation or idle session timeout. Well behaved backend code performs
+ * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never
+ * sleep using mechanisms other than the WaitEventSet mechanism or the more
+ * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for
+ * bounded short periods, eg LWLock waits), so they should react in good time.
+ *
+ * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
+ * consists of tasks that are safe to perform at most times.  They can be
+ * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
+ *
+ *
+ * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
+ *
+ * for (;;)
+ * {
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ *	   if (work to do)
+ *		   Do Stuff();
+ *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ * }
+ *
+ * It's important to clear the interrupt *before* checking if there's work to
+ * do. Otherwise, if someone sets the interrupt between the check and the
+ * ClearInterrupt() 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
+ *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ * }
+ *
+ * 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.
+ *
+ * To wake up the waiter, you must first set a global flag or something else
+ * that the wait loop tests in the "if (work to do)" part, and call
+ * SendInterrupt(INTERRUPT_GENERAL) *after* that. SendInterrupt is designed to
+ * return quickly if the interrupt is already set. In more complex scenarios
+ * with nested loops that can consume different events, you can define your
+ * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/storage/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef STORAGE_INTERRUPT_H
+#define STORAGE_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/waiteventset.h"
+
+#include <signal.h>
+
+extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
+
+/*
+ * Flags in the pending interrupts bitmask. Each value represents one bit in
+ * the bitmask.
+ */
+typedef enum
+{
+	/*
+	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+	 * waiting for an interrupt. If it's set, the backend needs to be woken up
+	 * when a bit in the pending interrupts mask is set. It's used internally
+	 * by the interrupt machinery, and cannot be used directly in the public
+	 * functions. It's named differently to distinguish it from the actual
+	 * interrupt flags.
+	 */
+	SLEEPING_ON_INTERRUPTS = 0,
+
+	/*
+	 * INTERRUPT_GENERAL is multiplexed for many reasons, like query
+	 * cancellation termination requests, recovery conflicts, and config
+	 * reload requests.  Upon receiving INTERRUPT_GENERAL, you should call
+	 * CHECK_FOR_INTERRUPTS() to process those requests.  It is also used for
+	 * various other context-dependent purposes, but note that if it's used to
+	 * wake up for other reasons, you must still call CHECK_FOR_INTERRUPTS()
+	 * once per iteration.
+	 */
+	INTERRUPT_GENERAL,
+
+	/*
+	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * it that it should continue WAL replay. It's sent by WAL receiver when
+	 * more WAL arrives, or when promotion is requested.
+	 */
+	INTERRUPT_RECOVERY_CONTINUE,
+} InterruptType;
+
+/*
+ * Test an interrupt flag.
+ */
+static inline bool
+InterruptIsPending(InterruptType reason)
+{
+	return (pg_atomic_read_u32(MyPendingInterrupts) & (1 << reason)) != 0;
+}
+
+/*
+ * Test an interrupt flag.
+ */
+static inline bool
+InterruptsPending(uint32 mask)
+{
+	return (pg_atomic_read_u32(MyPendingInterrupts) & (mask)) != 0;
+}
+
+/*
+ * Clear an interrupt flag.
+ */
+static inline void
+ClearInterrupt(InterruptType reason)
+{
+	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~(1 << reason));
+}
+
+/*
+ * Test and clear an interrupt flag.
+ */
+static inline bool
+ConsumeInterrupt(InterruptType reason)
+{
+	if (likely(!InterruptIsPending(reason)))
+		return false;
+
+	ClearInterrupt(reason);
+
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptType reason);
+extern void SendInterrupt(InterruptType reason, ProcNumber pgprocno);
+extern int	WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+#endif
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index 7e194d536f..0000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,196 +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.
- *
- *
- * WaitEventSets 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-2024, 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 "utils/resowner.h"
-
-/*
- * 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;
-
-/*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
- */
-#define WL_LATCH_SET		 (1 << 0)
-#define WL_SOCKET_READABLE	 (1 << 1)
-#define WL_SOCKET_WRITEABLE  (1 << 2)
-#define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
-#define WL_POSTMASTER_DEATH  (1 << 4)
-#define WL_EXIT_ON_PM_DEATH	 (1 << 5)
-#ifdef WIN32
-#define WL_SOCKET_CONNECTED  (1 << 6)
-#else
-/* avoid having to deal with case on platforms not requiring it */
-#define WL_SOCKET_CONNECTED  WL_SOCKET_WRITEABLE
-#endif
-#define WL_SOCKET_CLOSED 	 (1 << 7)
-#ifdef WIN32
-#define WL_SOCKET_ACCEPT	 (1 << 8)
-#else
-/* avoid having to deal with case on platforms not requiring it */
-#define WL_SOCKET_ACCEPT	WL_SOCKET_READABLE
-#endif
-#define WL_SOCKET_MASK		(WL_SOCKET_READABLE | \
-							 WL_SOCKET_WRITEABLE | \
-							 WL_SOCKET_CONNECTED | \
-							 WL_SOCKET_ACCEPT | \
-							 WL_SOCKET_CLOSED)
-
-typedef struct WaitEvent
-{
-	int			pos;			/* position in the event data structure */
-	uint32		events;			/* triggered events */
-	pgsocket	fd;				/* socket fd associated with event */
-	void	   *user_data;		/* pointer provided in AddWaitEventToSet */
-#ifdef WIN32
-	bool		reset;			/* Is reset of the event required? */
-#endif
-} WaitEvent;
-
-/* forward declaration to avoid exposing latch.c implementation details */
-typedef struct WaitEventSet WaitEventSet;
-
-/*
- * prototypes for functions in latch.c
- */
-extern void InitializeLatchSupport(void);
-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 void ShutdownLatchSupport(void);
-
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
-extern void FreeWaitEventSet(WaitEventSet *set);
-extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
-extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  Latch *latch, void *user_data);
-extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch);
-
-extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
-							 WaitEvent *occurred_events, int nevents,
-							 uint32 wait_event_info);
-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);
-extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
-extern bool WaitEventSetCanReportClosed(void);
-
-#endif							/* LATCH_H */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 5a3dd5d2d4..6060b57a0a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -17,7 +17,6 @@
 #include "access/clog.h"
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -172,9 +171,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -310,6 +306,14 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	pg_atomic_uint32 pendingInterrupts;
+	pg_atomic_uint32 maybeSleepingOnInterrupts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -425,6 +429,8 @@ typedef struct PROC_HDR
 	 */
 	ProcNumber	walwriterProc;
 	ProcNumber	checkpointerProc;
+	ProcNumber	walreceiverProc;
+	ProcNumber	startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -492,9 +498,6 @@ extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
 extern void CheckDeadLockAlert(void);
 extern void LockErrorCleanup(void);
 
-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/waiteventset.h b/src/include/storage/waiteventset.h
new file mode 100644
index 0000000000..ed34ff8b63
--- /dev/null
+++ b/src/include/storage/waiteventset.h
@@ -0,0 +1,120 @@
+/*-------------------------------------------------------------------------
+ *
+ * waiteventset.h
+ *	  ppoll() / pselect() like interface for waiting for events
+ *
+ * This is a reliable replacement for the common pattern of using pg_usleep()
+ * or select() to wait until a signal arrives, where the signal handler raises
+ * an interrupt (see storage/interrupt.h). 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. WaitEventSets and Interrupts are designed to
+ * overcome these limitations, allowing you to sleep without polling and
+ * ensuring quick response to signals from other processes.
+ *
+ * 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/interrupt.c for detailed specifications for the exported
+ * functions.
+ *
+ * On some platforms, signals will not interrupt the wait primitive by
+ * themselves.  Therefore, it is critical that any signal handler that is
+ * meant to terminate a WaitInterrupt wait calls RaiseInterrupt.
+ *
+ * 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 WaitInterrupt or WaitInterruptOrSocket.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/waiteventset.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAITEVENTSET_H
+#define WAITEVENTSET_H
+
+#include <signal.h>
+
+#include "storage/procnumber.h"
+#include "utils/resowner.h"
+
+/*
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
+ */
+#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() */
+#define WL_POSTMASTER_DEATH  (1 << 4)
+#define WL_EXIT_ON_PM_DEATH	 (1 << 5)
+#ifdef WIN32
+#define WL_SOCKET_CONNECTED  (1 << 6)
+#else
+/* avoid having to deal with case on platforms not requiring it */
+#define WL_SOCKET_CONNECTED  WL_SOCKET_WRITEABLE
+#endif
+#define WL_SOCKET_CLOSED 	 (1 << 7)
+#ifdef WIN32
+#define WL_SOCKET_ACCEPT	 (1 << 8)
+#else
+/* avoid having to deal with case on platforms not requiring it */
+#define WL_SOCKET_ACCEPT	WL_SOCKET_READABLE
+#endif
+#define WL_SOCKET_MASK		(WL_SOCKET_READABLE | \
+							 WL_SOCKET_WRITEABLE | \
+							 WL_SOCKET_CONNECTED | \
+							 WL_SOCKET_ACCEPT | \
+							 WL_SOCKET_CLOSED)
+
+typedef struct WaitEvent
+{
+	int			pos;			/* position in the event data structure */
+	uint32		events;			/* triggered events */
+	pgsocket	fd;				/* socket fd associated with event */
+	void	   *user_data;		/* pointer provided in AddWaitEventToSet */
+#ifdef WIN32
+	bool		reset;			/* Is reset of the event required? */
+#endif
+} WaitEvent;
+
+/* forward declaration to avoid exposing interrupt.c implementation details */
+typedef struct WaitEventSet WaitEventSet;
+
+struct PGPROC;
+
+/*
+ * prototypes for functions in waiteventset.c
+ */
+extern void InitializeWaitEventSupport(void);
+extern void ShutdownWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
+
+extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern void FreeWaitEventSet(WaitEventSet *set);
+extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
+extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
+							  uint32 interruptMask, void *user_data);
+extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, uint32 interruptMask);
+
+extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
+							 WaitEvent *occurred_events, int nevents,
+							 uint32 wait_event_info);
+extern void InitializeInterruptWaitSet(void);
+extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
+extern bool WaitEventSetCanReportClosed(void);
+
+/* low level functions used to implement SendInterrupt */
+extern void WakeupMyProc(void);
+extern void WakeupOtherProc(struct PGPROC *proc);
+
+#endif							/* WAITEVENTSET_H */
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 1284458bfc..9d8ef26129 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 set 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 e01c0c9de9..0307807889 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index fb23560439..25f4c33ff8 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -18,6 +18,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
+#include "storage/interrupt.h"
 #include "storage/shm_toc.h"
 #include "test_shm_mq.h"
 #include "utils/memutils.h"
@@ -285,11 +286,11 @@ 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);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 we_bgworker_startup);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 3d235568b8..c3efac5a8f 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -16,6 +16,7 @@
 #include "fmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "storage/interrupt.h"
 #include "varatt.h"
 
 #include "test_shm_mq.h"
@@ -234,13 +235,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 an 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 6c4fbc7827..67515e0fed 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -128,7 +129,7 @@ test_shm_mq_main(Datum main_arg)
 		elog(DEBUG1, "registrant backend has exited prematurely");
 		proc_exit(1);
 	}
-	SetLatch(&registrant->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
 
 	/* 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 d4403b24d9..df6039cd0d 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -5,7 +5,7 @@
  *		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.
+ *		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
@@ -26,7 +26,7 @@
 #include "miscadmin.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -223,15 +223,15 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 worker_spi_naptime * 1000L,
+							 worker_spi_wait_event_main);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 2d4c870423..e4922c21ad 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1254,6 +1254,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptType
 Interval
 IntervalAggState
 IntoClause
@@ -1492,7 +1493,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
-- 
2.39.5



  [text/x-patch] v5-0002-Fix-lost-wakeup-issue-in-logical-replication-laun.patch (4.1K, ../../[email protected]/3-v5-0002-Fix-lost-wakeup-issue-in-logical-replication-laun.patch)
  download | inline diff:
From cd0fe3346cabf337e7bb557404a8917855839750 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:18 +0200
Subject: [PATCH v5 2/4] Fix lost wakeup issue in logical replication launcher

Fix it by using a separate interrupt bit for subscription changes.

Discussion: https://www.postgresql.org/message-id/[email protected]
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 src/backend/replication/logical/launcher.c | 23 ++++++++++++++--------
 src/include/storage/interrupt.h            |  5 ++++-
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 9765a099ec..5122728dfb 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -57,7 +57,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;
@@ -814,7 +814,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;
 }
 
 /*
@@ -974,6 +974,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1119,8 +1120,12 @@ ApplyLauncherWakeupAtCommit(void)
 static 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);
 }
 
 /*
@@ -1134,8 +1139,8 @@ 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);
@@ -1167,6 +1172,7 @@ ApplyLauncherMain(Datum main_arg)
 		oldctx = MemoryContextSwitchTo(subctx);
 
 		/* Start any missing workers for enabled subscriptions. */
+		ClearInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE);
 		sublist = get_subscription_list();
 		foreach(lc, sublist)
 		{
@@ -1223,7 +1229,8 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL |
+						   1 << INTERRUPT_SUBSCRIPTION_CHANGE,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   wait_time,
 						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
@@ -1250,7 +1257,7 @@ ApplyLauncherMain(Datum main_arg)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/include/storage/interrupt.h b/src/include/storage/interrupt.h
index 1da05369c3..39815bf488 100644
--- a/src/include/storage/interrupt.h
+++ b/src/include/storage/interrupt.h
@@ -110,11 +110,14 @@ typedef enum
 	INTERRUPT_GENERAL,
 
 	/*
-	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * INTERRUPT_RECOVERY_CONTINUE is used to wake up startup process, to tell
 	 * it that it should continue WAL replay. It's sent by WAL receiver when
 	 * more WAL arrives, or when promotion is requested.
 	 */
 	INTERRUPT_RECOVERY_CONTINUE,
+
+	/* sent to logical replication launcher, when a subscription changes */
+	INTERRUPT_SUBSCRIPTION_CHANGE,
 } InterruptType;
 
 /*
-- 
2.39.5



  [text/x-patch] v5-0003-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch (23.6K, ../../[email protected]/4-v5-0003-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch)
  download | inline diff:
From d3a8cd03de89eb4184b9eb65267e80a922e71677 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:21 +0200
Subject: [PATCH v5 3/4] Use INTERRUPT_GENERAL 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_GENERAL sent directly by the postmaster when the
worker state changes.  Previously, the postmaster would send a SIGUSR1
signal, which relied on the ProcSignal system's handler setting the
interrupt flag, with the same end effect.  Now that intermediate step
is skipped.

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

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

This represents the first use of SendInterrupt() in the postmaster.
It might seem more natural to use condition variables in the
wait-for-state-change functions, so that anyone with a handle can
wait, but condition variables as currently implemented would be a lot
less robust than simple interrupts.

Author: Thomas Munro <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c            |   6 -
 doc/src/sgml/bgworker.sgml                  |  27 +++--
 src/backend/access/transam/parallel.c       |   1 -
 src/backend/postmaster/bgworker.c           | 128 +++++++++++---------
 src/backend/postmaster/postmaster.c         |   8 +-
 src/backend/replication/logical/launcher.c  |   2 -
 src/backend/storage/ipc/interrupt.c         |   3 +
 src/backend/storage/ipc/waiteventset.c      |   5 +
 src/include/postmaster/bgworker.h           |  10 +-
 src/include/postmaster/bgworker_internals.h |   4 +-
 src/test/modules/test_shm_mq/setup.c        |   3 +-
 src/test/modules/test_shm_mq/test_shm_mq.h  |   2 +
 src/test/modules/test_shm_mq/worker.c       |   9 +-
 src/test/modules/worker_spi/worker_spi.c    |   3 -
 14 files changed, 112 insertions(+), 99 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 7c291c996c..7b2926e66d 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -817,9 +817,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),
@@ -853,9 +850,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/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 3171054e55..871f6869d6 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>
@@ -108,6 +108,18 @@ 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_GENERAL</literal> when the workers state changes, which allows the
+       caller to wait for the worker to start and shut down.  That can be
+       suppressed by setting this flag.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -181,12 +193,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
@@ -258,10 +266,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/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 27c3db9e3c..984936aa00 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -600,7 +600,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.
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 828fc0b732..344cef2402 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -78,6 +78,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;
 
 /*
@@ -192,8 +194,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -234,6 +237,14 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -315,20 +326,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;
+			int			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;
@@ -336,8 +347,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_GENERAL, notify_proc_number);
 
 			continue;
 		}
@@ -383,23 +394,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;
@@ -421,7 +415,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.
  */
@@ -466,8 +460,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_GENERAL, slot->notify_proc_number);
 }
 
 /*
@@ -483,12 +477,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
@@ -501,27 +495,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_GENERAL, notify_proc_number);
 }
 
 /*
- * Cancel SIGUSR1 notifications for a PID belonging to an exiting backend.
+ * Cancel notifications for a PID 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;
+		}
 	}
 }
 
@@ -553,14 +554,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;
+			int			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_GENERAL, notify_proc_number);
 		}
 	}
 }
@@ -613,11 +614,6 @@ ResetBackgroundWorkerCrashTimes(void)
 			 * resetting.
 			 */
 			rw->rw_crashed_at = 0;
-
-			/*
-			 * If there was anyone waiting for it, they're history.
-			 */
-			rw->rw_worker.bgw_notify_pid = 0;
 		}
 	}
 }
@@ -981,15 +977,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
@@ -1105,6 +1092,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.
@@ -1205,8 +1211,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)
@@ -1250,8 +1257,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)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 4e28bfb2f9..f63ba7368a 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -3999,15 +3999,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_GENERAL, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 5122728dfb..56aeda8f15 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -505,7 +505,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))
@@ -948,7 +947,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);
diff --git a/src/backend/storage/ipc/interrupt.c b/src/backend/storage/ipc/interrupt.c
index 53473a352d..ec2fd6a3cf 100644
--- a/src/backend/storage/ipc/interrupt.c
+++ b/src/backend/storage/ipc/interrupt.c
@@ -102,6 +102,9 @@ RaiseInterrupt(InterruptType reason)
 
 /*
  * 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.
  */
 void
 SendInterrupt(InterruptType reason, ProcNumber pgprocno)
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index ed3758cd4a..9d10bc5fcd 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -568,6 +568,11 @@ WakeupMyProc(void)
 void
 WakeupOtherProc(PGPROC *proc)
 {
+	/*
+	 * 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
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 22fc49ec27..8ad4a12728 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -59,6 +59,14 @@
  */
 #define BGWORKER_BACKEND_DATABASE_CONNECTION		0x0002
 
+/*
+ * 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							0x0004
+
 /*
  * 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
@@ -97,7 +105,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 f55adc85ef..db30e3fd6a 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 extern void BackgroundWorkerMain(char *startup_data, size_t startup_data_len) pg_attribute_noreturn();
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 25f4c33ff8..8c81c2b4d3 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -134,6 +134,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;
@@ -223,8 +224,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)
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 0ae7bd64cd..7f27a61aff 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 67515e0fed..e5d353d11b 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -53,7 +53,6 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
 	 * Establish signal handlers.
@@ -123,13 +122,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);
-	}
-	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
+	SendInterrupt(INTERRUPT_GENERAL, 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 df6039cd0d..58b81e5dc7 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -376,7 +376,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.
@@ -425,8 +424,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);
-- 
2.39.5



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-01-28 17:01   ` Andres Freund <[email protected]>
  2025-01-28 21:15     ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-02-28 20:24     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 3 replies; 22+ messages in thread

From: Andres Freund @ 2025-01-28 17:01 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

On 2024-12-02 16:39:28 +0200, Heikki Linnakangas wrote:
> From eff8de11fbfea4e2aadce9c1d71452b0f5a1b80b Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Mon, 2 Dec 2024 15:51:54 +0200
> Subject: [PATCH v5 1/4] Replace Latches with Interrupts

Your email has only three attachements - I assume the fourth is something
local that didn't need to be shared?


> MIME-Version: 1.0
> Content-Type: text/plain; charset=UTF-8
> Content-Transfer-Encoding: 8bit
> 
> The Latch was a flag, with an inter-process signalling mechanism that
> allowed a process to wait for the Latch to be set. My original vision
> for Latches was that you could have different latches for different
> things that you need to wait for, but in practice, almost all code
> used one "process latch" that was shared for all wakeups. The only
> exception was the "recoveryWakeupLatch". I think the reason that we
> ended up just sharing the same latch for all wakeups is that it was
> cumbersome in practice to deal with multiple latches. For starters,
> there was no machinery for waiting for multiple latches at the same
> time. Secondly, changing the "ownership" of a latch needed extra
> locking or other coordination. Thirdly, each inter-process latch
> needed to be initialized at postmaster startup time.
> 
> This patch embraces the reality of how Latches were used, and replaces
> the Latches with a per-process interrupt mask. You can no longer
> allocate Latches in arbitrary shared memory structs and an interrupt
> is always directed at a particular process, addressed by its
> ProcNumber.  Each process has a bitmask of pending interrupts in
> PGPROC.

That doesn't really remove the need for inter-process coordination to know
what ProcNumber corresponds to what...


> This commit introduces two interrupt bits. INTERRUPT_GENERAL replaces
> the general-purpose per-process latch. All code that previously set a
> process's process latch now sets its INTERRUPT_GENERAL interrupt bit
> instead.

Half-formed-at-best thought: I wonder if we should split the "interrupt
yourself in response to a signal" cases from "another process wakes you up",
even in the initial commit. They seem rather different to me.


> This also moves the WaitEventSet functions to a different source file,
> waiteventset.c. This separates the platform-dependent code waiting and
> signalling code from the platform-independent parts.

I think this should be split into a separate commit. It's painful to verify
that code-movement didn't change anything else if there's lots of other
changes in the same commit.


> diff --git a/src/include/storage/interrupt.h b/src/include/storage/interrupt.h
> new file mode 100644
> index 0000000000..1da05369c3
> --- /dev/null
> +++ b/src/include/storage/interrupt.h

So we now have postmaster/interrupt.[ch] and storage/interrupt.h /
storage/ipc/interrupt.c.


> @@ -0,0 +1,171 @@
> +/*-------------------------------------------------------------------------
> + *
> + * interrupt.h
> + *	  Interrupt handling routines.

And all of them use the same "short description".

That somehow doesn't seem optimal, although I don't really have a good
solution.



> + * "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.
> + *
> + * Interrupt flags can be "raised" synchronously by code that wants to defer
> + * an action, or asynchronously by timer signal handlers, other signal
> + * handlers or "sent" by other backends setting them directly.

I think it might be worth explicitly stating here that multiple "arriving"
interrupts can be coalesced.


> + * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is
> + * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query
> + * cancellation or idle session timeout. Well behaved backend code performs
> + * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never
> + * sleep using mechanisms other than the WaitEventSet mechanism or the more
> + * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for
> + * bounded short periods, eg LWLock waits), so they should react in good time.




> + * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
> + * consists of tasks that are safe to perform at most times.  They can be
> + * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
> + *
> + *
> + * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
> + *
> + * for (;;)
> + * {
> + *	   ClearInterrupt(INTERRUPT_GENERAL);
> + *	   if (work to do)
> + *		   Do Stuff();
> + *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
> + * }

I don't particularly like that there's now dozens of places that need to do
bit-shifting.

One reason I don't like it is that, for example, this should actually be 1U <<
INTERRUPT_GENERAL, and at some later point we might need it to be a 64bit
integer due to a larger number of interrupts, which will require going to all
extensions and updating them.

Perhaps WaitInterrupt should accept just a single interrupt type and
WaitEventSets should allow registering/reporting different interrupts to wait
for as different events?

Or perhaps we could introduce an interrupt mask type?


> + * To wake up the waiter, you must first set a global flag or something else
> + * that the wait loop tests in the "if (work to do)" part, and call
> + * SendInterrupt(INTERRUPT_GENERAL) *after* that. SendInterrupt is designed to
> + * return quickly if the interrupt is already set. In more complex scenarios
> + * with nested loops that can consume different events, you can define your
> + * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL.
> + *
> + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
> + * Portions Copyright (c) 1994, Regents of the University of California
> + *
> + * IDENTIFICATION
> + *	  src/include/storage/interrupt.h
> + *
> + *-------------------------------------------------------------------------
> + */

This is really rather similar to procsignal.h. I think at the very least there
should be some mention in either file to make the delineation clearer.



> +#ifndef STORAGE_INTERRUPT_H
> +#define STORAGE_INTERRUPT_H
> +
> +#include "port/atomics.h"
> +#include "storage/procnumber.h"
> +#include "storage/waiteventset.h"
> +
> +#include <signal.h>

Do we actually need signal.h here? I think that's from latch.h, which needed
it for sig_atomic_t, but we don't use that anymore here.


> +extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
> +
> +/*
> + * Flags in the pending interrupts bitmask. Each value represents one bit in
> + * the bitmask.
> + */
> +typedef enum

Personally I prefer if we name the objects underlying typedefs, as otherwise
some tools will label them "anonymous". And, while it doesn't matter for
enums, which we can't forward declare in our version of C, it also is required
to make forward declarations work.



> +/*
> + * Test an interrupt flag.
> + */
> +static inline bool
> +InterruptIsPending(InterruptType reason)
> +{
> +	return (pg_atomic_read_u32(MyPendingInterrupts) & (1 << reason)) != 0;
> +}

This has no memory ordering guarantees implied - is that correct?  I think
that could lead to missing interrupts in some cases.

Given functions like ClearInterrupt() are <Verb><Object>, why differ here?


> +/*
> + * Test an interrupt flag.
> + */
> +static inline bool
> +InterruptsPending(uint32 mask)
> +{
> +	return (pg_atomic_read_u32(MyPendingInterrupts) & (mask)) != 0;
> +}

It seems somewhat confusing to have two naming patterns for this and the
closely related InterruptIsPending().

Maybe IsInterruptPending() and AreInterruptsPending()? Or
IsInterruptPendingMask()?

I think I like IsInterruptPendingMask() better, because we're not actually
waiting for multiple interrupts, we're waiting for one out of multiple
possible interrupts to arrive



> +/*
> + * Clear an interrupt flag.
> + */
> +static inline void
> +ClearInterrupt(InterruptType reason)
> +{
> +	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~(1 << reason));
> +}
> +
> +/*
> + * Test and clear an interrupt flag.
> + */
> +static inline bool
> +ConsumeInterrupt(InterruptType reason)
> +{
> +	if (likely(!InterruptIsPending(reason)))
> +		return false;
> +
> +	ClearInterrupt(reason);
> +
> +	return true;
> +}

I think some guidance when which of these two should be used would be good.


> +extern void RaiseInterrupt(InterruptType reason);
> +extern void SendInterrupt(InterruptType reason, ProcNumber pgprocno);
> +extern int	WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
> +						  uint32 wait_event_info);
> +extern int	WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
> +								  long timeout, uint32 wait_event_info);
> +extern void SwitchToLocalInterrupts(void);
> +extern void SwitchToSharedInterrupts(void);
> +extern void InitializeInterruptWaitSet(void);
> +
> +#endif



> @@ -4476,7 +4458,10 @@ CheckPromoteSignal(void)
>  void
>  WakeupRecovery(void)
>  {
> -	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
> +	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
> +
> +	if (procno != INVALID_PROC_NUMBER)
> +		SendInterrupt(INTERRUPT_RECOVERY_CONTINUE, procno);
>  }

Not a new problem, and not really a problem for the startup process, that we
don't expect to die, but: Code like this could obviously lead to interrupts
being sent to the "former" owner of a procno.

I think either the file header of SendInterrupt() should mention that risk and
that code has to be aware of it.

Should we explicitly define the interrupt state a process has at startup?


> +/*
> + * Switch to local interrupts.  Other backends can't send interrupts to this
> + * one.  Only RaiseInterrupt() can set them, from inside this process.
> + */
> +void
> +SwitchToLocalInterrupts(void)
> +{
> +	if (MyPendingInterrupts == &LocalPendingInterrupts)
> +		return;
> +
> +	MyPendingInterrupts = &LocalPendingInterrupts;
> +
> +	/*
> +	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
> +	 * seeing the new MyPendingInterrupts destination.
> +	 */
> +	pg_memory_barrier();

I don't think you need a memory barrier between signal handler and normal
code.  A compiler barrier, sure, sometimes also something to protect against
partial writes. but memory barriers shouldn't be needed.


> +/*
> + * Set an interrupt flag in this backend.
> + */
> +void
> +RaiseInterrupt(InterruptType reason)

> +{
> +	uint32		old_pending;
> +
> +	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << reason);
> +
> +	/*
> +	 * If the process is currently blocked waiting for an interrupt to arrive,
> +	 * and the interrupt wasn't already pending, wake it up.
> +	 */
> +	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)

This is a somewhat hard to read line. Maybe it's worth breaking it out into
two conditions? Or use a local variable?

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2025-01-28 21:15     ` Andres Freund <[email protected]>
  2025-01-28 22:04       ` Re: Interrupts vs signals Thomas Munro <[email protected]>
  2 siblings, 1 reply; 22+ messages in thread

From: Andres Freund @ 2025-01-28 21:15 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

I was chatting with Heikki about this patch and he mentioned that he recalls a
patch that did some work to unify the signal replacement, procsignal.h and
CHECK_FOR_INTERRUPTS(). Thomas, that was probably from you? Do you have a
pointer, if so?

It does seem like we're going to have to do some unification here. We have too
many different partially overlapping, partially collaborating systems here.


- procsignals - kinda like the interrupts here, but not quite. Probably can't
  just merge them 1:1 into the proposed mechanism, or we'll run out of bits
  rather soon.  I don't know if we want the relevant multiplexing to be built
  into interrupt.h or not.

  Or we ought to redesign this mechanism to deal with more than 32 types of
  interrupt.


- pmsignal.h - basically the same thing as here, except for signals sent *too*
  postmaster.

  It might or might not be required to keep this separate. There are different
  "reliability" requirements...


- CHECK_FOR_INTERRUPTS(), which uses the mechanism introduced here to react to
  signals while blocked, using RaiseInterrupt() (whereas it did SetLatch()
  before).

  A fairly simple improvement would be to use different InterruptType for
  e.g. termination and query cancellation.

  But even with this infrastructure in place, we can't *quite* get away from
  signal handlers, because we need some way to set at the very least
  InterruptPending (and perhaps ProcDiePending, QueryCancelPending etc,
  although those could be replaced with InterruptIsPending()). The
  infrastructure introduced with these patches provides neither a way to set
  those variables in response to delivery of an interrupt, nor can we
  currently set them from another backend, as they are global variables.

  We could of course do InterruptsPending(~0) in in
  INTERRUPTS_PENDING_CONDITION(), but it would require analyzing the increased
  indirection overhead as well as the "timeliness" guarantees.

  Today we don't need a memory barrier around checking InterruptPending,
  because delivery of a signal delivery (via SetLatch()) ensures that. But I
  think we would need one if we were to not send signals for
  cancellation/terminations etc. I.e. right now the overhead of delivery is
  bigger, but checking if there pending signals is cheaper.


  Another aspect of this is that we're cramming more and more code into
  ProcessInterrupts(), in a long weave of ifs.  I wonder if somewhere along
  ipc/interrupt.h there should be a facility to register callbacks to react to
  delivered interrupts.  It'd need to be a bit more than just a mapping of
  InterruptType to callback, because of things like InterruptHoldoffCount,
  CritSectionCount, QueryCancelHoldoffCount.


- timeout.c - will need a fairly big renovation for threading.  IIUC Heikki is
  thinking that we'll have a dedicated timer thread.

  It's a lot more expensive to wake up another thread than your own. A process
  local SIGALRM delivery does not require an inter processor interrupt, but
  doing a pthread_sigqueue() or whatnot does. Which would be a bit silly,
  because what most of the timeout handlers do is to set some variables to
  true and then call SetLatch().  It might not matter *that* much, because we
  don't except timeout "delivery" that frequently, but I'm also not convinced
  it won't be noticeable.

  Nearly all the timeouts we currently have could actually just specify the
  interrupt that ought to be raised, to be sent by that timeout thread. The
  one exception to that is StartupPacketTimeoutHandler() (which does
  _exit(1)).

  Without that one exception, a renovated timeout.c would not need to support
  handlers, which would be rather nice.  It's not at all obvious to me that we
  actually need StartupPacketTimeoutHandler() and process_startup_packet_die()
  actually need to do _exit(), we should be able to do non-blocking network IO
  around these parts - at least for SSL we largely do, we just don't check for
  interupts in in the right places.


Other questions:

- Can ipc/interrupts.c style interrupts be sent by postmaster? I think they
  ought to before long.


Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-01-28 21:15     ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2025-01-28 22:04       ` Thomas Munro <[email protected]>
  0 siblings, 0 replies; 22+ messages in thread

From: Thomas Munro @ 2025-01-28 22:04 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Heikki Linnakangas <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Wed, Jan 29, 2025 at 10:15 AM Andres Freund <[email protected]> wrote:
> I was chatting with Heikki about this patch and he mentioned that he recalls a
> patch that did some work to unify the signal replacement, procsignal.h and
> CHECK_FOR_INTERRUPTS(). Thomas, that was probably from you? Do you have a
> pointer, if so?

Yeah, attempts at that were included in earlier versions in this very
thread, but then Heikki came up with the
let's-just-replace-latches-completely concept and rejiggered the lower
level patches around that (which I liked and support).  I will try to
rebase/reorganise the accompanying procsignal removal part on top of
this version today, more soon...  (I had meant to do that earlier but
got a bit distracted by summer holiday season down here and some
personal stuff, sorry for the delay on that).

> It does seem like we're going to have to do some unification here. We have too
> many different partially overlapping, partially collaborating systems here.

Right, my goal from the start of this thread was always a full
unification leaving just one single system for this type of IPC, and
Heikki's latest version is a transitional point; hopefully with the
other stuff rebased on top we'll be getting pretty close to that, at
least for the procsignal part.  More soon.






^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2025-02-28 20:24     ` Heikki Linnakangas <[email protected]>
  2025-03-05 19:25       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2025-02-28 20:24 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 28/01/2025 19:01, Andres Freund wrote:
> On 2024-12-02 16:39:28 +0200, Heikki Linnakangas wrote:
>> This also moves the WaitEventSet functions to a different source file,
>> waiteventset.c. This separates the platform-dependent code waiting and
>> signalling code from the platform-independent parts.
> 
> I think this should be split into a separate commit. It's painful to verify
> that code-movement didn't change anything else if there's lots of other
> changes in the same commit.

Here's a patch set to do just that, split WaitEventSet stuff to a 
separate source file. That's actually a pretty nice separation even 
without any of the rest of the patches.

I noticed that the ShutdownLatchSupport() function is unused. The first 
patch removes it.

The second patch makes it possible to use ModifyWaitEvent() to switch 
between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH. WaitLatch() used to 
modify WaitEventSet->exit_on_postmaster_death directly, now it uses 
ModifyWaitEvent() for that. That's needed because with the final patch, 
WaitLatch() is in a different source file than WaitEventSet, so it 
cannot directly modify its field anymore.

The third patch is mechanical and moves existing code. The file header 
comments in the modified files are perhaps worth reviewing. They are 
also just existing text moved around, but there was some small decisions 
on what exactly should go where.

I'll continue working on the other parts, but these patches seems ready 
for commit already.

-- 
Heikki Linnakangas
Neon (https://neon.tech)


Attachments:

  [text/x-patch] 0001-Remove-unused-ShutdownLatchSupport-function.patch (1.8K, ../../[email protected]/2-0001-Remove-unused-ShutdownLatchSupport-function.patch)
  download | inline diff:
From 5540a635f2198c41a26f38a6057bbf146b9291d3 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 28 Feb 2025 16:56:48 +0200
Subject: [PATCH 1/3] Remove unused ShutdownLatchSupport() function

The only caller was removed in commit 80a8f95b3b. I don't foresee
needing it any time soon, and I'm working on some big changes in this
area, so let's remove it out of the way.
---
 src/backend/storage/ipc/latch.c | 27 ---------------------------
 src/include/storage/latch.h     |  1 -
 2 files changed, 28 deletions(-)

diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
index 4572684f224..cd09a10f3d1 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/latch.c
@@ -360,33 +360,6 @@ InitializeLatchWaitSet(void)
 	Assert(latch_pos == LatchWaitSetLatchPos);
 }
 
-void
-ShutdownLatchSupport(void)
-{
-#if defined(WAIT_USE_POLL)
-	pqsignal(SIGURG, SIG_IGN);
-#endif
-
-	if (LatchWaitSet)
-	{
-		FreeWaitEventSet(LatchWaitSet);
-		LatchWaitSet = NULL;
-	}
-
-#if defined(WAIT_USE_SELF_PIPE)
-	close(selfpipe_readfd);
-	close(selfpipe_writefd);
-	selfpipe_readfd = -1;
-	selfpipe_writefd = -1;
-	selfpipe_owner_pid = InvalidPid;
-#endif
-
-#if defined(WAIT_USE_SIGNALFD)
-	close(signal_fd);
-	signal_fd = -1;
-#endif
-}
-
 /*
  * Initialize a process-local latch.
  */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
index bf568124df9..66e7a5b7c08 100644
--- a/src/include/storage/latch.h
+++ b/src/include/storage/latch.h
@@ -173,7 +173,6 @@ extern void OwnLatch(Latch *latch);
 extern void DisownLatch(Latch *latch);
 extern void SetLatch(Latch *latch);
 extern void ResetLatch(Latch *latch);
-extern void ShutdownLatchSupport(void);
 
 extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
-- 
2.39.5



  [text/x-patch] 0002-Use-ModifyWaitEvent-to-update-exit_on_postmaster_dea.patch (3.4K, ../../[email protected]/3-0002-Use-ModifyWaitEvent-to-update-exit_on_postmaster_dea.patch)
  download | inline diff:
From 2e65230485d84b685858c196f5d64b83bfe3bb94 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 28 Feb 2025 21:19:08 +0200
Subject: [PATCH 2/3] Use ModifyWaitEvent to update exit_on_postmaster_death

This is in preparation for splitting WaitEventSet related functions to
a separate source file. That will hide the details of WaitEventSet
from WaitLatch, so it must use an exposed function instead of
modifying WaitEventSet->exit_on_postmaster_death directly.
---
 src/backend/storage/ipc/latch.c | 42 +++++++++++++++++++++++----------
 1 file changed, 30 insertions(+), 12 deletions(-)

diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
index cd09a10f3d1..ab601c748f8 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/latch.c
@@ -154,8 +154,9 @@ struct WaitEventSet
 /* A common WaitEventSet used to implement WaitLatch() */
 static WaitEventSet *LatchWaitSet;
 
-/* The position of the latch in LatchWaitSet. */
+/* The positions of the latch and PM death events in LatchWaitSet */
 #define LatchWaitSetLatchPos 0
+#define LatchWaitSetPostmasterDeathPos 1
 
 #ifndef WIN32
 /* Are we currently in WaitLatch? The signal handler would like to know. */
@@ -353,11 +354,18 @@ InitializeLatchWaitSet(void)
 	LatchWaitSet = CreateWaitEventSet(NULL, 2);
 	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
 								  MyLatch, NULL);
-	if (IsUnderPostmaster)
-		AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-						  PGINVALID_SOCKET, NULL, 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);
+	}
 }
 
 /*
@@ -505,8 +513,14 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
 	if (!(wakeEvents & WL_LATCH_SET))
 		latch = NULL;
 	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-	LatchWaitSet->exit_on_postmaster_death =
-		((wakeEvents & WL_EXIT_ON_PM_DEATH) != 0);
+
+	/*
+	 * Update the event set for whether WL_EXIT_ON_PM_DEATH or
+	 * WL_POSTMASTER_DEATH was requested.  This is also cheap.
+	 */
+	ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
 
 	if (WaitEventSetWait(LatchWaitSet,
 						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
@@ -1037,15 +1051,19 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 		(!(event->events & WL_LATCH_SET) || set->latch == latch))
 		return;
 
-	if (event->events & WL_LATCH_SET &&
-		events != event->events)
+	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
+	if (event->events & WL_POSTMASTER_DEATH)
 	{
-		elog(ERROR, "cannot modify latch event");
+		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
+			elog(ERROR, "cannot modify postmaster death event");
+		set->exit_on_postmaster_death = ((events & WL_EXIT_ON_PM_DEATH) != 0);
+		return;
 	}
 
-	if (event->events & WL_POSTMASTER_DEATH)
+	if (event->events & WL_LATCH_SET &&
+		events != event->events)
 	{
-		elog(ERROR, "cannot modify postmaster death event");
+		elog(ERROR, "cannot modify latch event");
 	}
 
 	/* FIXME: validate event mask */
-- 
2.39.5



  [text/x-patch] 0003-Split-WaitEventSet-functions-to-separate-source-file.patch (126.4K, ../../[email protected]/4-0003-Split-WaitEventSet-functions-to-separate-source-file.patch)
  download | inline diff:
From 7bd0d2f98a1b9d7ad40f3f72cd1c93430c1d7cee Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 28 Feb 2025 16:42:15 +0200
Subject: [PATCH 3/3] Split WaitEventSet functions to separate source file

latch.c now only contains the Latch related functions, which build on
the WaitEventSet abstraction. Most of the platform-dependent stuff is
now in waiteventset.c.
---
 src/backend/libpq/pqsignal.c           |    2 +-
 src/backend/postmaster/postmaster.c    |    2 +-
 src/backend/storage/ipc/Makefile       |    3 +-
 src/backend/storage/ipc/latch.c        | 1999 +----------------------
 src/backend/storage/ipc/meson.build    |    1 +
 src/backend/storage/ipc/waiteventset.c | 2033 ++++++++++++++++++++++++
 src/backend/utils/init/miscinit.c      |    4 +-
 src/include/storage/latch.h            |   66 +-
 src/include/storage/waiteventset.h     |   95 ++
 9 files changed, 2149 insertions(+), 2056 deletions(-)
 create mode 100644 src/backend/storage/ipc/waiteventset.c
 create mode 100644 src/include/storage/waiteventset.h

diff --git a/src/backend/libpq/pqsignal.c b/src/backend/libpq/pqsignal.c
index 1742e90ea9e..d866307a4dc 100644
--- a/src/backend/libpq/pqsignal.c
+++ b/src/backend/libpq/pqsignal.c
@@ -42,7 +42,7 @@ pqinitmask(void)
 {
 	sigemptyset(&UnBlockSig);
 
-	/* Note: InitializeLatchSupport() modifies UnBlockSig. */
+	/* Note: InitializeWaitEventSupport() modifies UnBlockSig. */
 
 	/* First set all signals, then clear some. */
 	sigfillset(&BlockSig);
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 5dd3b6a4fd4..d2a7a7add6f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -548,7 +548,7 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGCHLD, handle_pm_child_exit_signal);
 
 	/* This may configure SIGURG, depending on platform. */
-	InitializeLatchSupport();
+	InitializeWaitEventSupport();
 	InitProcessLocalLatch();
 
 	/*
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index d8a1653eb6a..9a07f6e1d92 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -25,6 +25,7 @@ OBJS = \
 	signalfuncs.o \
 	sinval.o \
 	sinvaladt.o \
-	standby.o
+	standby.o \
+	waiteventset.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
index ab601c748f8..aae0cf7577d 100644
--- a/src/backend/storage/ipc/latch.c
+++ b/src/backend/storage/ipc/latch.c
@@ -3,25 +3,10 @@
  * latch.c
  *	  Routines for inter-process latches
  *
- * 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 epoll() implementation overcomes the race with a different technique: it
- * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
- * don't need to register a signal handler or create our own self-pipe.  We
- * assume that any system that has Linux epoll() also has Linux signalfd().
- *
- * The kqueue() implementation waits for SIGURG with EVFILT_SIGNAL.
- *
- * The Windows implementation uses Windows events that are inherited by all
- * postmaster child processes. There's no need for the self-pipe trick there.
+ * 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-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -33,124 +18,12 @@
  */
 #include "postgres.h"
 
-#include <fcntl.h>
-#include <limits.h>
-#include <signal.h>
-#include <unistd.h>
-#ifdef HAVE_SYS_EPOLL_H
-#include <sys/epoll.h>
-#endif
-#ifdef HAVE_SYS_EVENT_H
-#include <sys/event.h>
-#endif
-#ifdef HAVE_SYS_SIGNALFD_H
-#include <sys/signalfd.h>
-#endif
-#ifdef HAVE_POLL_H
-#include <poll.h>
-#endif
-
-#include "libpq/pqsignal.h"
 #include "miscadmin.h"
-#include "pgstat.h"
 #include "port/atomics.h"
-#include "portability/instr_time.h"
-#include "postmaster/postmaster.h"
-#include "storage/fd.h"
-#include "storage/ipc.h"
 #include "storage/latch.h"
-#include "storage/pmsignal.h"
-#include "utils/memutils.h"
+#include "storage/waiteventset.h"
 #include "utils/resowner.h"
 
-/*
- * Select the fd readiness primitive to use. Normally the "most modern"
- * primitive supported by the OS will be used, but for testing it can be
- * useful to manually specify the used primitive.  If desired, just add a
- * define somewhere before this block.
- */
-#if defined(WAIT_USE_EPOLL) || defined(WAIT_USE_POLL) || \
-	defined(WAIT_USE_KQUEUE) || defined(WAIT_USE_WIN32)
-/* don't overwrite manual choice */
-#elif defined(HAVE_SYS_EPOLL_H)
-#define WAIT_USE_EPOLL
-#elif defined(HAVE_KQUEUE)
-#define WAIT_USE_KQUEUE
-#elif defined(HAVE_POLL)
-#define WAIT_USE_POLL
-#elif WIN32
-#define WAIT_USE_WIN32
-#else
-#error "no wait set implementation available"
-#endif
-
-/*
- * By default, we use a self-pipe with poll() and a signalfd with epoll(), if
- * available.  For testing the choice can also be manually specified.
- */
-#if defined(WAIT_USE_POLL) || defined(WAIT_USE_EPOLL)
-#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
-/* don't overwrite manual choice */
-#elif defined(WAIT_USE_EPOLL) && defined(HAVE_SYS_SIGNALFD_H)
-#define WAIT_USE_SIGNALFD
-#else
-#define WAIT_USE_SELF_PIPE
-#endif
-#endif
-
-/* typedef in latch.h */
-struct WaitEventSet
-{
-	ResourceOwner owner;
-
-	int			nevents;		/* number of registered events */
-	int			nevents_space;	/* maximum number of events in this set */
-
-	/*
-	 * Array, of nevents_space length, storing the definition of events this
-	 * set is waiting for.
-	 */
-	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.
-	 */
-	Latch	   *latch;
-	int			latch_pos;
-
-	/*
-	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
-	 * is set so that we'll exit immediately if postmaster death is detected,
-	 * instead of returning.
-	 */
-	bool		exit_on_postmaster_death;
-
-#if defined(WAIT_USE_EPOLL)
-	int			epoll_fd;
-	/* epoll_wait returns events in a user provided arrays, allocate once */
-	struct epoll_event *epoll_ret_events;
-#elif defined(WAIT_USE_KQUEUE)
-	int			kqueue_fd;
-	/* kevent returns events in a user provided arrays, allocate once */
-	struct kevent *kqueue_ret_events;
-	bool		report_postmaster_not_running;
-#elif defined(WAIT_USE_POLL)
-	/* poll expects events to be waited on every poll() call, prepare once */
-	struct pollfd *pollfds;
-#elif defined(WAIT_USE_WIN32)
-
-	/*
-	 * Array of windows events. The first element always contains
-	 * pgwin32_signal_event, so the remaining elements are offset by one (i.e.
-	 * event->pos + 1).
-	 */
-	HANDLE	   *handles;
-#endif
-};
-
 /* A common WaitEventSet used to implement WaitLatch() */
 static WaitEventSet *LatchWaitSet;
 
@@ -158,191 +31,6 @@ static WaitEventSet *LatchWaitSet;
 #define LatchWaitSetLatchPos 0
 #define LatchWaitSetPostmasterDeathPos 1
 
-#ifndef WIN32
-/* Are we currently in WaitLatch? 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. */
-static int	signal_fd = -1;
-#endif
-
-#ifdef WAIT_USE_SELF_PIPE
-/* Read and write ends of the self-pipe */
-static int	selfpipe_readfd = -1;
-static int	selfpipe_writefd = -1;
-
-/* Process owning the self-pipe --- needed for checking purposes */
-static int	selfpipe_owner_pid = 0;
-
-/* Private function prototypes */
-static void latch_sigurg_handler(SIGNAL_ARGS);
-static void sendSelfPipeByte(void);
-#endif
-
-#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
-static void drain(void);
-#endif
-
-#if defined(WAIT_USE_EPOLL)
-static void WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action);
-#elif defined(WAIT_USE_KQUEUE)
-static void WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events);
-#elif defined(WAIT_USE_POLL)
-static void WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event);
-#elif defined(WAIT_USE_WIN32)
-static void WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event);
-#endif
-
-static inline int WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
-										WaitEvent *occurred_events, int nevents);
-
-/* ResourceOwner support to hold WaitEventSets */
-static void ResOwnerReleaseWaitEventSet(Datum res);
-
-static const ResourceOwnerDesc wait_event_set_resowner_desc =
-{
-	.name = "WaitEventSet",
-	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
-	.release_priority = RELEASE_PRIO_WAITEVENTSETS,
-	.ReleaseResource = ResOwnerReleaseWaitEventSet,
-	.DebugPrint = NULL
-};
-
-/* Convenience wrappers over ResourceOwnerRemember/Forget */
-static inline void
-ResourceOwnerRememberWaitEventSet(ResourceOwner owner, WaitEventSet *set)
-{
-	ResourceOwnerRemember(owner, PointerGetDatum(set), &wait_event_set_resowner_desc);
-}
-static inline void
-ResourceOwnerForgetWaitEventSet(ResourceOwner owner, WaitEventSet *set)
-{
-	ResourceOwnerForget(owner, PointerGetDatum(set), &wait_event_set_resowner_desc);
-}
-
-
-/*
- * Initialize the process-local latch infrastructure.
- *
- * This must be called once during startup of any process that can wait on
- * latches, before it issues any InitLatch() or OwnLatch() calls.
- */
-void
-InitializeLatchSupport(void)
-{
-#if defined(WAIT_USE_SELF_PIPE)
-	int			pipefd[2];
-
-	if (IsUnderPostmaster)
-	{
-		/*
-		 * We might have inherited connections to a self-pipe created by the
-		 * postmaster.  It's critical that child processes create their own
-		 * self-pipes, of course, and we really want them to close the
-		 * inherited FDs for safety's sake.
-		 */
-		if (selfpipe_owner_pid != 0)
-		{
-			/* Assert we go through here but once in a child process */
-			Assert(selfpipe_owner_pid != MyProcPid);
-			/* Release postmaster's pipe FDs; ignore any error */
-			(void) close(selfpipe_readfd);
-			(void) close(selfpipe_writefd);
-			/* Clean up, just for safety's sake; we'll set these below */
-			selfpipe_readfd = selfpipe_writefd = -1;
-			selfpipe_owner_pid = 0;
-			/* Keep fd.c's accounting straight */
-			ReleaseExternalFD();
-			ReleaseExternalFD();
-		}
-		else
-		{
-			/*
-			 * Postmaster didn't create a self-pipe ... or else we're in an
-			 * EXEC_BACKEND build, in which case it doesn't matter since the
-			 * postmaster's pipe FDs were closed by the action of FD_CLOEXEC.
-			 * fd.c won't have state to clean up, either.
-			 */
-			Assert(selfpipe_readfd == -1);
-		}
-	}
-	else
-	{
-		/* In postmaster or standalone backend, assert we do this but once */
-		Assert(selfpipe_readfd == -1);
-		Assert(selfpipe_owner_pid == 0);
-	}
-
-	/*
-	 * 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.
-	 */
-	if (pipe(pipefd) < 0)
-		elog(FATAL, "pipe() failed: %m");
-	if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1)
-		elog(FATAL, "fcntl(F_SETFL) failed on read-end of self-pipe: %m");
-	if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) == -1)
-		elog(FATAL, "fcntl(F_SETFL) failed on write-end of self-pipe: %m");
-	if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1)
-		elog(FATAL, "fcntl(F_SETFD) failed on read-end of self-pipe: %m");
-	if (fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1)
-		elog(FATAL, "fcntl(F_SETFD) failed on write-end of self-pipe: %m");
-
-	selfpipe_readfd = pipefd[0];
-	selfpipe_writefd = pipefd[1];
-	selfpipe_owner_pid = MyProcPid;
-
-	/* Tell fd.c about these two long-lived FDs */
-	ReserveExternalFD();
-	ReserveExternalFD();
-
-	pqsignal(SIGURG, latch_sigurg_handler);
-#endif
-
-#ifdef WAIT_USE_SIGNALFD
-	sigset_t	signalfd_mask;
-
-	if (IsUnderPostmaster)
-	{
-		/*
-		 * It would probably be safe to re-use the inherited signalfd since
-		 * signalfds only see the current process's pending signals, but it
-		 * seems less surprising to close it and create our own.
-		 */
-		if (signal_fd != -1)
-		{
-			/* Release postmaster's signal FD; ignore any error */
-			(void) close(signal_fd);
-			signal_fd = -1;
-			ReleaseExternalFD();
-		}
-	}
-
-	/* Block SIGURG, because we'll receive it through a signalfd. */
-	sigaddset(&UnBlockSig, SIGURG);
-
-	/* Set up the signalfd to receive SIGURG notifications. */
-	sigemptyset(&signalfd_mask);
-	sigaddset(&signalfd_mask, SIGURG);
-	signal_fd = signalfd(-1, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC);
-	if (signal_fd < 0)
-		elog(FATAL, "signalfd() failed");
-	ReserveExternalFD();
-#endif
-
-#ifdef WAIT_USE_KQUEUE
-	/* Ignore SIGURG, because we'll receive it via kqueue. */
-	pqsignal(SIGURG, SIG_IGN);
-#endif
-}
-
 void
 InitializeLatchWaitSet(void)
 {
@@ -379,13 +67,7 @@ InitLatch(Latch *latch)
 	latch->owner_pid = MyProcPid;
 	latch->is_shared = false;
 
-#if defined(WAIT_USE_SELF_PIPE)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
-#elif defined(WAIT_USE_SIGNALFD)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(signal_fd >= 0);
-#elif defined(WAIT_USE_WIN32)
+#ifdef WAIT_USE_WIN32
 	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
 	if (latch->event == NULL)
 		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
@@ -448,14 +130,6 @@ OwnLatch(Latch *latch)
 	/* Sanity checks */
 	Assert(latch->is_shared);
 
-#if defined(WAIT_USE_SELF_PIPE)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(selfpipe_readfd >= 0 && selfpipe_owner_pid == MyProcPid);
-#elif defined(WAIT_USE_SIGNALFD)
-	/* Assert InitializeLatchSupport has been called in this process */
-	Assert(signal_fd >= 0);
-#endif
-
 	owner_pid = latch->owner_pid;
 	if (owner_pid != 0)
 		elog(PANIC, "latch already owned by PID %d", owner_pid);
@@ -669,17 +343,9 @@ SetLatch(Latch *latch)
 	if (owner_pid == 0)
 		return;
 	else if (owner_pid == MyProcPid)
-	{
-#if defined(WAIT_USE_SELF_PIPE)
-		if (waiting)
-			sendSelfPipeByte();
-#else
-		if (waiting)
-			kill(MyProcPid, SIGURG);
-#endif
-	}
+		WakeupMyProc();
 	else
-		kill(owner_pid, SIGURG);
+		WakeupOtherProc(owner_pid);
 
 #else
 
@@ -724,1652 +390,3 @@ ResetLatch(Latch *latch)
 	 */
 	pg_memory_barrier();
 }
-
-/*
- * Create a WaitEventSet with space for nevents different events to wait for.
- *
- * These events can then be efficiently waited upon together, using
- * WaitEventSetWait().
- *
- * The WaitEventSet is tracked by the given 'resowner'.  Use NULL for session
- * lifetime.
- */
-WaitEventSet *
-CreateWaitEventSet(ResourceOwner resowner, int nevents)
-{
-	WaitEventSet *set;
-	char	   *data;
-	Size		sz = 0;
-
-	/*
-	 * Use MAXALIGN size/alignment to guarantee that later uses of memory are
-	 * aligned correctly. E.g. epoll_event might need 8 byte alignment on some
-	 * platforms, but earlier allocations like WaitEventSet and WaitEvent
-	 * might not be sized to guarantee that when purely using sizeof().
-	 */
-	sz += MAXALIGN(sizeof(WaitEventSet));
-	sz += MAXALIGN(sizeof(WaitEvent) * nevents);
-
-#if defined(WAIT_USE_EPOLL)
-	sz += MAXALIGN(sizeof(struct epoll_event) * nevents);
-#elif defined(WAIT_USE_KQUEUE)
-	sz += MAXALIGN(sizeof(struct kevent) * nevents);
-#elif defined(WAIT_USE_POLL)
-	sz += MAXALIGN(sizeof(struct pollfd) * nevents);
-#elif defined(WAIT_USE_WIN32)
-	/* need space for the pgwin32_signal_event */
-	sz += MAXALIGN(sizeof(HANDLE) * (nevents + 1));
-#endif
-
-	if (resowner != NULL)
-		ResourceOwnerEnlarge(resowner);
-
-	data = (char *) MemoryContextAllocZero(TopMemoryContext, sz);
-
-	set = (WaitEventSet *) data;
-	data += MAXALIGN(sizeof(WaitEventSet));
-
-	set->events = (WaitEvent *) data;
-	data += MAXALIGN(sizeof(WaitEvent) * nevents);
-
-#if defined(WAIT_USE_EPOLL)
-	set->epoll_ret_events = (struct epoll_event *) data;
-	data += MAXALIGN(sizeof(struct epoll_event) * nevents);
-#elif defined(WAIT_USE_KQUEUE)
-	set->kqueue_ret_events = (struct kevent *) data;
-	data += MAXALIGN(sizeof(struct kevent) * nevents);
-#elif defined(WAIT_USE_POLL)
-	set->pollfds = (struct pollfd *) data;
-	data += MAXALIGN(sizeof(struct pollfd) * nevents);
-#elif defined(WAIT_USE_WIN32)
-	set->handles = (HANDLE) data;
-	data += MAXALIGN(sizeof(HANDLE) * nevents);
-#endif
-
-	set->latch = NULL;
-	set->nevents_space = nevents;
-	set->exit_on_postmaster_death = false;
-
-	if (resowner != NULL)
-	{
-		ResourceOwnerRememberWaitEventSet(resowner, set);
-		set->owner = resowner;
-	}
-
-#if defined(WAIT_USE_EPOLL)
-	if (!AcquireExternalFD())
-		elog(ERROR, "AcquireExternalFD, for epoll_create1, failed: %m");
-	set->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
-	if (set->epoll_fd < 0)
-	{
-		ReleaseExternalFD();
-		elog(ERROR, "epoll_create1 failed: %m");
-	}
-#elif defined(WAIT_USE_KQUEUE)
-	if (!AcquireExternalFD())
-		elog(ERROR, "AcquireExternalFD, for kqueue, failed: %m");
-	set->kqueue_fd = kqueue();
-	if (set->kqueue_fd < 0)
-	{
-		ReleaseExternalFD();
-		elog(ERROR, "kqueue failed: %m");
-	}
-	if (fcntl(set->kqueue_fd, F_SETFD, FD_CLOEXEC) == -1)
-	{
-		int			save_errno = errno;
-
-		close(set->kqueue_fd);
-		ReleaseExternalFD();
-		errno = save_errno;
-		elog(ERROR, "fcntl(F_SETFD) failed on kqueue descriptor: %m");
-	}
-	set->report_postmaster_not_running = false;
-#elif defined(WAIT_USE_WIN32)
-
-	/*
-	 * To handle signals while waiting, we need to add a win32 specific event.
-	 * We accounted for the additional event at the top of this routine. See
-	 * port/win32/signal.c for more details.
-	 *
-	 * Note: pgwin32_signal_event should be first to ensure that it will be
-	 * reported when multiple events are set.  We want to guarantee that
-	 * pending signals are serviced.
-	 */
-	set->handles[0] = pgwin32_signal_event;
-	StaticAssertStmt(WSA_INVALID_EVENT == NULL, "");
-#endif
-
-	return set;
-}
-
-/*
- * Free a previously created WaitEventSet.
- *
- * Note: preferably, this shouldn't have to free any resources that could be
- * inherited across an exec().  If it did, we'd likely leak those resources in
- * many scenarios.  For the epoll case, we ensure that by setting EPOLL_CLOEXEC
- * when the FD is created.  For the Windows case, we assume that the handles
- * involved are non-inheritable.
- */
-void
-FreeWaitEventSet(WaitEventSet *set)
-{
-	if (set->owner)
-	{
-		ResourceOwnerForgetWaitEventSet(set->owner, set);
-		set->owner = NULL;
-	}
-
-#if defined(WAIT_USE_EPOLL)
-	close(set->epoll_fd);
-	ReleaseExternalFD();
-#elif defined(WAIT_USE_KQUEUE)
-	close(set->kqueue_fd);
-	ReleaseExternalFD();
-#elif defined(WAIT_USE_WIN32)
-	for (WaitEvent *cur_event = set->events;
-		 cur_event < (set->events + set->nevents);
-		 cur_event++)
-	{
-		if (cur_event->events & WL_LATCH_SET)
-		{
-			/* uses the latch's HANDLE */
-		}
-		else if (cur_event->events & WL_POSTMASTER_DEATH)
-		{
-			/* uses PostmasterHandle */
-		}
-		else
-		{
-			/* Clean up the event object we created for the socket */
-			WSAEventSelect(cur_event->fd, NULL, 0);
-			WSACloseEvent(set->handles[cur_event->pos + 1]);
-		}
-	}
-#endif
-
-	pfree(set);
-}
-
-/*
- * Free a previously created WaitEventSet in a child process after a fork().
- */
-void
-FreeWaitEventSetAfterFork(WaitEventSet *set)
-{
-#if defined(WAIT_USE_EPOLL)
-	close(set->epoll_fd);
-	ReleaseExternalFD();
-#elif defined(WAIT_USE_KQUEUE)
-	/* kqueues are not normally inherited by child processes */
-	ReleaseExternalFD();
-#endif
-
-	pfree(set);
-}
-
-/* ---
- * Add an event to the set. Possible events are:
- * - WL_LATCH_SET: Wait for the latch to be set
- * - 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
- * - WL_SOCKET_WRITEABLE: Wait for socket to become writeable,
- *	 can be combined with other WL_SOCKET_* events
- * - WL_SOCKET_CONNECTED: Wait for socket connection to be established,
- *	 can be combined with other WL_SOCKET_* events (on non-Windows
- *	 platforms, this is the same as WL_SOCKET_WRITEABLE)
- * - WL_SOCKET_ACCEPT: Wait for new connection to a server socket,
- *	 can be combined with other WL_SOCKET_* events (on non-Windows
- *	 platforms, this is the same as WL_SOCKET_READABLE)
- * - WL_SOCKET_CLOSED: Wait for socket to be closed by remote peer.
- * - WL_EXIT_ON_PM_DEATH: Exit immediately if the postmaster dies
- *
- * 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.
- *
- * The user_data pointer specified here will be set for the events returned
- * by WaitEventSetWait(), allowing to easily associate additional data with
- * events.
- */
-int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
-				  void *user_data)
-{
-	WaitEvent  *event;
-
-	/* not enough space */
-	Assert(set->nevents < set->nevents_space);
-
-	if (events == WL_EXIT_ON_PM_DEATH)
-	{
-		events = WL_POSTMASTER_DEATH;
-		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
-	{
-		if (events & WL_LATCH_SET)
-			elog(ERROR, "cannot wait on latch without a specified latch");
-	}
-
-	/* waiting for socket readiness without a socket indicates a bug */
-	if (fd == PGINVALID_SOCKET && (events & WL_SOCKET_MASK))
-		elog(ERROR, "cannot wait on socket event without a socket");
-
-	event = &set->events[set->nevents];
-	event->pos = set->nevents++;
-	event->fd = fd;
-	event->events = events;
-	event->user_data = user_data;
-#ifdef WIN32
-	event->reset = false;
-#endif
-
-	if (events == WL_LATCH_SET)
-	{
-		set->latch = latch;
-		set->latch_pos = event->pos;
-#if defined(WAIT_USE_SELF_PIPE)
-		event->fd = selfpipe_readfd;
-#elif defined(WAIT_USE_SIGNALFD)
-		event->fd = signal_fd;
-#else
-		event->fd = PGINVALID_SOCKET;
-#ifdef WAIT_USE_EPOLL
-		return event->pos;
-#endif
-#endif
-	}
-	else if (events == WL_POSTMASTER_DEATH)
-	{
-#ifndef WIN32
-		event->fd = postmaster_alive_fds[POSTMASTER_FD_WATCH];
-#endif
-	}
-
-	/* perform wait primitive specific initialization, if needed */
-#if defined(WAIT_USE_EPOLL)
-	WaitEventAdjustEpoll(set, event, EPOLL_CTL_ADD);
-#elif defined(WAIT_USE_KQUEUE)
-	WaitEventAdjustKqueue(set, event, 0);
-#elif defined(WAIT_USE_POLL)
-	WaitEventAdjustPoll(set, event);
-#elif defined(WAIT_USE_WIN32)
-	WaitEventAdjustWin32(set, event);
-#endif
-
-	return event->pos;
-}
-
-/*
- * 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.
- *
- * 'pos' is the id returned by AddWaitEventToSet.
- */
-void
-ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
-{
-	WaitEvent  *event;
-#if defined(WAIT_USE_KQUEUE)
-	int			old_events;
-#endif
-
-	Assert(pos < set->nevents);
-
-	event = &set->events[pos];
-#if defined(WAIT_USE_KQUEUE)
-	old_events = event->events;
-#endif
-
-	/*
-	 * If neither the event mask nor the associated latch 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))
-		return;
-
-	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
-	if (event->events & WL_POSTMASTER_DEATH)
-	{
-		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
-			elog(ERROR, "cannot modify postmaster death event");
-		set->exit_on_postmaster_death = ((events & WL_EXIT_ON_PM_DEATH) != 0);
-		return;
-	}
-
-	if (event->events & WL_LATCH_SET &&
-		events != event->events)
-	{
-		elog(ERROR, "cannot modify latch event");
-	}
-
-	/* FIXME: validate event mask */
-	event->events = events;
-
-	if (events == WL_LATCH_SET)
-	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
-
-		/*
-		 * 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.
-		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
-		return;
-#endif
-	}
-
-#if defined(WAIT_USE_EPOLL)
-	WaitEventAdjustEpoll(set, event, EPOLL_CTL_MOD);
-#elif defined(WAIT_USE_KQUEUE)
-	WaitEventAdjustKqueue(set, event, old_events);
-#elif defined(WAIT_USE_POLL)
-	WaitEventAdjustPoll(set, event);
-#elif defined(WAIT_USE_WIN32)
-	WaitEventAdjustWin32(set, event);
-#endif
-}
-
-#if defined(WAIT_USE_EPOLL)
-/*
- * action can be one of EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL
- */
-static void
-WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
-{
-	struct epoll_event epoll_ev;
-	int			rc;
-
-	/* pointer to our event, returned by epoll_wait */
-	epoll_ev.data.ptr = event;
-	/* always wait for errors */
-	epoll_ev.events = EPOLLERR | EPOLLHUP;
-
-	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
-	{
-		Assert(set->latch != NULL);
-		epoll_ev.events |= EPOLLIN;
-	}
-	else if (event->events == WL_POSTMASTER_DEATH)
-	{
-		epoll_ev.events |= EPOLLIN;
-	}
-	else
-	{
-		Assert(event->fd != PGINVALID_SOCKET);
-		Assert(event->events & (WL_SOCKET_READABLE |
-								WL_SOCKET_WRITEABLE |
-								WL_SOCKET_CLOSED));
-
-		if (event->events & WL_SOCKET_READABLE)
-			epoll_ev.events |= EPOLLIN;
-		if (event->events & WL_SOCKET_WRITEABLE)
-			epoll_ev.events |= EPOLLOUT;
-		if (event->events & WL_SOCKET_CLOSED)
-			epoll_ev.events |= EPOLLRDHUP;
-	}
-
-	/*
-	 * Even though unused, we also pass epoll_ev as the data argument if
-	 * EPOLL_CTL_DEL is passed as action.  There used to be an epoll bug
-	 * requiring that, and actually it makes the code simpler...
-	 */
-	rc = epoll_ctl(set->epoll_fd, action, event->fd, &epoll_ev);
-
-	if (rc < 0)
-		ereport(ERROR,
-				(errcode_for_socket_access(),
-				 errmsg("%s() failed: %m",
-						"epoll_ctl")));
-}
-#endif
-
-#if defined(WAIT_USE_POLL)
-static void
-WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
-{
-	struct pollfd *pollfd = &set->pollfds[event->pos];
-
-	pollfd->revents = 0;
-	pollfd->fd = event->fd;
-
-	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
-	{
-		Assert(set->latch != NULL);
-		pollfd->events = POLLIN;
-	}
-	else if (event->events == WL_POSTMASTER_DEATH)
-	{
-		pollfd->events = POLLIN;
-	}
-	else
-	{
-		Assert(event->events & (WL_SOCKET_READABLE |
-								WL_SOCKET_WRITEABLE |
-								WL_SOCKET_CLOSED));
-		pollfd->events = 0;
-		if (event->events & WL_SOCKET_READABLE)
-			pollfd->events |= POLLIN;
-		if (event->events & WL_SOCKET_WRITEABLE)
-			pollfd->events |= POLLOUT;
-#ifdef POLLRDHUP
-		if (event->events & WL_SOCKET_CLOSED)
-			pollfd->events |= POLLRDHUP;
-#endif
-	}
-
-	Assert(event->fd != PGINVALID_SOCKET);
-}
-#endif
-
-#if defined(WAIT_USE_KQUEUE)
-
-/*
- * On most BSD family systems, the udata member of struct kevent is of type
- * void *, so we could directly convert to/from WaitEvent *.  Unfortunately,
- * NetBSD has it as intptr_t, so here we wallpaper over that difference with
- * an lvalue cast.
- */
-#define AccessWaitEvent(k_ev) (*((WaitEvent **)(&(k_ev)->udata)))
-
-static inline void
-WaitEventAdjustKqueueAdd(struct kevent *k_ev, int filter, int action,
-						 WaitEvent *event)
-{
-	k_ev->ident = event->fd;
-	k_ev->filter = filter;
-	k_ev->flags = action;
-	k_ev->fflags = 0;
-	k_ev->data = 0;
-	AccessWaitEvent(k_ev) = event;
-}
-
-static inline void
-WaitEventAdjustKqueueAddPostmaster(struct kevent *k_ev, WaitEvent *event)
-{
-	/* For now postmaster death can only be added, not removed. */
-	k_ev->ident = PostmasterPid;
-	k_ev->filter = EVFILT_PROC;
-	k_ev->flags = EV_ADD;
-	k_ev->fflags = NOTE_EXIT;
-	k_ev->data = 0;
-	AccessWaitEvent(k_ev) = event;
-}
-
-static inline void
-WaitEventAdjustKqueueAddLatch(struct kevent *k_ev, WaitEvent *event)
-{
-	/* For now latch can only be added, not removed. */
-	k_ev->ident = SIGURG;
-	k_ev->filter = EVFILT_SIGNAL;
-	k_ev->flags = EV_ADD;
-	k_ev->fflags = 0;
-	k_ev->data = 0;
-	AccessWaitEvent(k_ev) = event;
-}
-
-/*
- * old_events is the previous event mask, used to compute what has changed.
- */
-static void
-WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
-{
-	int			rc;
-	struct kevent k_ev[2];
-	int			count = 0;
-	bool		new_filt_read = false;
-	bool		old_filt_read = false;
-	bool		new_filt_write = false;
-	bool		old_filt_write = false;
-
-	if (old_events == event->events)
-		return;
-
-	Assert(event->events != WL_LATCH_SET || set->latch != NULL);
-	Assert(event->events == WL_LATCH_SET ||
-		   event->events == WL_POSTMASTER_DEATH ||
-		   (event->events & (WL_SOCKET_READABLE |
-							 WL_SOCKET_WRITEABLE |
-							 WL_SOCKET_CLOSED)));
-
-	if (event->events == WL_POSTMASTER_DEATH)
-	{
-		/*
-		 * Unlike all the other implementations, we detect postmaster death
-		 * using process notification instead of waiting on the postmaster
-		 * alive pipe.
-		 */
-		WaitEventAdjustKqueueAddPostmaster(&k_ev[count++], event);
-	}
-	else if (event->events == WL_LATCH_SET)
-	{
-		/* We detect latch wakeup using a signal event. */
-		WaitEventAdjustKqueueAddLatch(&k_ev[count++], event);
-	}
-	else
-	{
-		/*
-		 * We need to compute the adds and deletes required to get from the
-		 * old event mask to the new event mask, since kevent treats readable
-		 * and writable as separate events.
-		 */
-		if (old_events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
-			old_filt_read = true;
-		if (event->events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
-			new_filt_read = true;
-		if (old_events & WL_SOCKET_WRITEABLE)
-			old_filt_write = true;
-		if (event->events & WL_SOCKET_WRITEABLE)
-			new_filt_write = true;
-		if (old_filt_read && !new_filt_read)
-			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_READ, EV_DELETE,
-									 event);
-		else if (!old_filt_read && new_filt_read)
-			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_READ, EV_ADD,
-									 event);
-		if (old_filt_write && !new_filt_write)
-			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_WRITE, EV_DELETE,
-									 event);
-		else if (!old_filt_write && new_filt_write)
-			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_WRITE, EV_ADD,
-									 event);
-	}
-
-	/* For WL_SOCKET_READ -> WL_SOCKET_CLOSED, no change needed. */
-	if (count == 0)
-		return;
-
-	Assert(count <= 2);
-
-	rc = kevent(set->kqueue_fd, &k_ev[0], count, NULL, 0, NULL);
-
-	/*
-	 * When adding the postmaster's pid, we have to consider that it might
-	 * already have exited and perhaps even been replaced by another process
-	 * with the same pid.  If so, we have to defer reporting this as an event
-	 * until the next call to WaitEventSetWaitBlock().
-	 */
-
-	if (rc < 0)
-	{
-		if (event->events == WL_POSTMASTER_DEATH &&
-			(errno == ESRCH || errno == EACCES))
-			set->report_postmaster_not_running = true;
-		else
-			ereport(ERROR,
-					(errcode_for_socket_access(),
-					 errmsg("%s() failed: %m",
-							"kevent")));
-	}
-	else if (event->events == WL_POSTMASTER_DEATH &&
-			 PostmasterPid != getppid() &&
-			 !PostmasterIsAlive())
-	{
-		/*
-		 * The extra PostmasterIsAliveInternal() check prevents false alarms
-		 * on systems that give a different value for getppid() while being
-		 * traced by a debugger.
-		 */
-		set->report_postmaster_not_running = true;
-	}
-}
-
-#endif
-
-#if defined(WAIT_USE_WIN32)
-static void
-WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event)
-{
-	HANDLE	   *handle = &set->handles[event->pos + 1];
-
-	if (event->events == WL_LATCH_SET)
-	{
-		Assert(set->latch != NULL);
-		*handle = set->latch->event;
-	}
-	else if (event->events == WL_POSTMASTER_DEATH)
-	{
-		*handle = PostmasterHandle;
-	}
-	else
-	{
-		int			flags = FD_CLOSE;	/* always check for errors/EOF */
-
-		if (event->events & WL_SOCKET_READABLE)
-			flags |= FD_READ;
-		if (event->events & WL_SOCKET_WRITEABLE)
-			flags |= FD_WRITE;
-		if (event->events & WL_SOCKET_CONNECTED)
-			flags |= FD_CONNECT;
-		if (event->events & WL_SOCKET_ACCEPT)
-			flags |= FD_ACCEPT;
-
-		if (*handle == WSA_INVALID_EVENT)
-		{
-			*handle = WSACreateEvent();
-			if (*handle == WSA_INVALID_EVENT)
-				elog(ERROR, "failed to create event for socket: error code %d",
-					 WSAGetLastError());
-		}
-		if (WSAEventSelect(event->fd, *handle, flags) != 0)
-			elog(ERROR, "failed to set up event for socket: error code %d",
-				 WSAGetLastError());
-
-		Assert(event->fd != PGINVALID_SOCKET);
-	}
-}
-#endif
-
-/*
- * Wait for events added to the set to happen, or until the timeout is
- * reached.  At most nevents occurred events are returned.
- *
- * If timeout = -1, block until an event occurs; if 0, check sockets for
- * readiness, but don't block; if > 0, block for at most timeout milliseconds.
- *
- * Returns the number of events occurred, or 0 if the timeout was reached.
- *
- * Returned events will have the fd, pos, user_data fields set to the
- * values associated with the registered event.
- */
-int
-WaitEventSetWait(WaitEventSet *set, long timeout,
-				 WaitEvent *occurred_events, int nevents,
-				 uint32 wait_event_info)
-{
-	int			returned_events = 0;
-	instr_time	start_time;
-	instr_time	cur_time;
-	long		cur_timeout = -1;
-
-	Assert(nevents > 0);
-
-	/*
-	 * Initialize timeout if requested.  We must record the current time so
-	 * that we can determine the remaining timeout if interrupted.
-	 */
-	if (timeout >= 0)
-	{
-		INSTR_TIME_SET_CURRENT(start_time);
-		Assert(timeout >= 0 && timeout <= INT_MAX);
-		cur_timeout = timeout;
-	}
-	else
-		INSTR_TIME_SET_ZERO(start_time);
-
-	pgstat_report_wait_start(wait_event_info);
-
-#ifndef WIN32
-	waiting = true;
-#else
-	/* Ensure that signals are serviced even if latch is already set */
-	pgwin32_dispatch_queued_signals();
-#endif
-	while (returned_events == 0)
-	{
-		int			rc;
-
-		/*
-		 * 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.
-		 */
-		if (set->latch && !set->latch->is_set)
-		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
-		}
-
-		if (set->latch && set->latch->is_set)
-		{
-			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
-			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
-			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.
-			 */
-			cur_timeout = 0;
-			timeout = 0;
-		}
-
-		/*
-		 * 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.
-		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
-
-		if (set->latch &&
-			set->latch->maybe_sleeping)
-			set->latch->maybe_sleeping = false;
-
-		if (rc == -1)
-			break;				/* timeout occurred */
-		else
-			returned_events += rc;
-
-		/* If we're not done, update cur_timeout for next iteration */
-		if (returned_events == 0 && timeout >= 0)
-		{
-			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
-	waiting = false;
-#endif
-
-	pgstat_report_wait_end();
-
-	return returned_events;
-}
-
-
-#if defined(WAIT_USE_EPOLL)
-
-/*
- * Wait using linux's epoll_wait(2).
- *
- * This is the preferable wait method, as several readiness notifications are
- * delivered, without having to iterate through all of set->events. The return
- * epoll_event struct contain a pointer to our events, making association
- * easy.
- */
-static inline int
-WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
-					  WaitEvent *occurred_events, int nevents)
-{
-	int			returned_events = 0;
-	int			rc;
-	WaitEvent  *cur_event;
-	struct epoll_event *cur_epoll_event;
-
-	/* Sleep */
-	rc = epoll_wait(set->epoll_fd, set->epoll_ret_events,
-					Min(nevents, set->nevents_space), cur_timeout);
-
-	/* Check return code */
-	if (rc < 0)
-	{
-		/* EINTR is okay, otherwise complain */
-		if (errno != EINTR)
-		{
-			waiting = false;
-			ereport(ERROR,
-					(errcode_for_socket_access(),
-					 errmsg("%s() failed: %m",
-							"epoll_wait")));
-		}
-		return 0;
-	}
-	else if (rc == 0)
-	{
-		/* timeout exceeded */
-		return -1;
-	}
-
-	/*
-	 * At least one event occurred, iterate over the returned epoll events
-	 * until they're either all processed, or we've returned all the events
-	 * the caller desired.
-	 */
-	for (cur_epoll_event = set->epoll_ret_events;
-		 cur_epoll_event < (set->epoll_ret_events + rc) &&
-		 returned_events < nevents;
-		 cur_epoll_event++)
-	{
-		/* epoll's data pointer is set to the associated WaitEvent */
-		cur_event = (WaitEvent *) cur_epoll_event->data.ptr;
-
-		occurred_events->pos = cur_event->pos;
-		occurred_events->user_data = cur_event->user_data;
-		occurred_events->events = 0;
-
-		if (cur_event->events == WL_LATCH_SET &&
-			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
-		{
-			/* Drain the signalfd. */
-			drain();
-
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
-			{
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events == WL_POSTMASTER_DEATH &&
-				 cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
-		{
-			/*
-			 * We expect an EPOLLHUP when the remote end is closed, but
-			 * because we don't expect the pipe to become readable or to have
-			 * any errors either, treat those cases as postmaster death, too.
-			 *
-			 * Be paranoid about a spurious event signaling the postmaster as
-			 * being dead.  There have been reports about that happening with
-			 * older primitives (select(2) to be specific), and a spurious
-			 * WL_POSTMASTER_DEATH event would be painful. Re-checking doesn't
-			 * cost much.
-			 */
-			if (!PostmasterIsAliveInternal())
-			{
-				if (set->exit_on_postmaster_death)
-					proc_exit(1);
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_POSTMASTER_DEATH;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events & (WL_SOCKET_READABLE |
-									  WL_SOCKET_WRITEABLE |
-									  WL_SOCKET_CLOSED))
-		{
-			Assert(cur_event->fd != PGINVALID_SOCKET);
-
-			if ((cur_event->events & WL_SOCKET_READABLE) &&
-				(cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP)))
-			{
-				/* data available in socket, or EOF */
-				occurred_events->events |= WL_SOCKET_READABLE;
-			}
-
-			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
-				(cur_epoll_event->events & (EPOLLOUT | EPOLLERR | EPOLLHUP)))
-			{
-				/* writable, or EOF */
-				occurred_events->events |= WL_SOCKET_WRITEABLE;
-			}
-
-			if ((cur_event->events & WL_SOCKET_CLOSED) &&
-				(cur_epoll_event->events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)))
-			{
-				/* remote peer shut down, or error */
-				occurred_events->events |= WL_SOCKET_CLOSED;
-			}
-
-			if (occurred_events->events != 0)
-			{
-				occurred_events->fd = cur_event->fd;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-	}
-
-	return returned_events;
-}
-
-#elif defined(WAIT_USE_KQUEUE)
-
-/*
- * Wait using kevent(2) on BSD-family systems and macOS.
- *
- * For now this mirrors the epoll code, but in future it could modify the fd
- * set in the same call to kevent as it uses for waiting instead of doing that
- * with separate system calls.
- */
-static int
-WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
-					  WaitEvent *occurred_events, int nevents)
-{
-	int			returned_events = 0;
-	int			rc;
-	WaitEvent  *cur_event;
-	struct kevent *cur_kqueue_event;
-	struct timespec timeout;
-	struct timespec *timeout_p;
-
-	if (cur_timeout < 0)
-		timeout_p = NULL;
-	else
-	{
-		timeout.tv_sec = cur_timeout / 1000;
-		timeout.tv_nsec = (cur_timeout % 1000) * 1000000;
-		timeout_p = &timeout;
-	}
-
-	/*
-	 * Report postmaster events discovered by WaitEventAdjustKqueue() or an
-	 * earlier call to WaitEventSetWait().
-	 */
-	if (unlikely(set->report_postmaster_not_running))
-	{
-		if (set->exit_on_postmaster_death)
-			proc_exit(1);
-		occurred_events->fd = PGINVALID_SOCKET;
-		occurred_events->events = WL_POSTMASTER_DEATH;
-		return 1;
-	}
-
-	/* Sleep */
-	rc = kevent(set->kqueue_fd, NULL, 0,
-				set->kqueue_ret_events,
-				Min(nevents, set->nevents_space),
-				timeout_p);
-
-	/* Check return code */
-	if (rc < 0)
-	{
-		/* EINTR is okay, otherwise complain */
-		if (errno != EINTR)
-		{
-			waiting = false;
-			ereport(ERROR,
-					(errcode_for_socket_access(),
-					 errmsg("%s() failed: %m",
-							"kevent")));
-		}
-		return 0;
-	}
-	else if (rc == 0)
-	{
-		/* timeout exceeded */
-		return -1;
-	}
-
-	/*
-	 * At least one event occurred, iterate over the returned kqueue events
-	 * until they're either all processed, or we've returned all the events
-	 * the caller desired.
-	 */
-	for (cur_kqueue_event = set->kqueue_ret_events;
-		 cur_kqueue_event < (set->kqueue_ret_events + rc) &&
-		 returned_events < nevents;
-		 cur_kqueue_event++)
-	{
-		/* kevent's udata points to the associated WaitEvent */
-		cur_event = AccessWaitEvent(cur_kqueue_event);
-
-		occurred_events->pos = cur_event->pos;
-		occurred_events->user_data = cur_event->user_data;
-		occurred_events->events = 0;
-
-		if (cur_event->events == WL_LATCH_SET &&
-			cur_kqueue_event->filter == EVFILT_SIGNAL)
-		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
-			{
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events == WL_POSTMASTER_DEATH &&
-				 cur_kqueue_event->filter == EVFILT_PROC &&
-				 (cur_kqueue_event->fflags & NOTE_EXIT) != 0)
-		{
-			/*
-			 * The kernel will tell this kqueue object only once about the
-			 * exit of the postmaster, so let's remember that for next time so
-			 * that we provide level-triggered semantics.
-			 */
-			set->report_postmaster_not_running = true;
-
-			if (set->exit_on_postmaster_death)
-				proc_exit(1);
-			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->events = WL_POSTMASTER_DEATH;
-			occurred_events++;
-			returned_events++;
-		}
-		else if (cur_event->events & (WL_SOCKET_READABLE |
-									  WL_SOCKET_WRITEABLE |
-									  WL_SOCKET_CLOSED))
-		{
-			Assert(cur_event->fd >= 0);
-
-			if ((cur_event->events & WL_SOCKET_READABLE) &&
-				(cur_kqueue_event->filter == EVFILT_READ))
-			{
-				/* readable, or EOF */
-				occurred_events->events |= WL_SOCKET_READABLE;
-			}
-
-			if ((cur_event->events & WL_SOCKET_CLOSED) &&
-				(cur_kqueue_event->filter == EVFILT_READ) &&
-				(cur_kqueue_event->flags & EV_EOF))
-			{
-				/* the remote peer has shut down */
-				occurred_events->events |= WL_SOCKET_CLOSED;
-			}
-
-			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
-				(cur_kqueue_event->filter == EVFILT_WRITE))
-			{
-				/* writable, or EOF */
-				occurred_events->events |= WL_SOCKET_WRITEABLE;
-			}
-
-			if (occurred_events->events != 0)
-			{
-				occurred_events->fd = cur_event->fd;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-	}
-
-	return returned_events;
-}
-
-#elif defined(WAIT_USE_POLL)
-
-/*
- * Wait using poll(2).
- *
- * This allows to receive readiness notifications for several events at once,
- * but requires iterating through all of set->pollfds.
- */
-static inline int
-WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
-					  WaitEvent *occurred_events, int nevents)
-{
-	int			returned_events = 0;
-	int			rc;
-	WaitEvent  *cur_event;
-	struct pollfd *cur_pollfd;
-
-	/* Sleep */
-	rc = poll(set->pollfds, set->nevents, (int) cur_timeout);
-
-	/* Check return code */
-	if (rc < 0)
-	{
-		/* EINTR is okay, otherwise complain */
-		if (errno != EINTR)
-		{
-			waiting = false;
-			ereport(ERROR,
-					(errcode_for_socket_access(),
-					 errmsg("%s() failed: %m",
-							"poll")));
-		}
-		return 0;
-	}
-	else if (rc == 0)
-	{
-		/* timeout exceeded */
-		return -1;
-	}
-
-	for (cur_event = set->events, cur_pollfd = set->pollfds;
-		 cur_event < (set->events + set->nevents) &&
-		 returned_events < nevents;
-		 cur_event++, cur_pollfd++)
-	{
-		/* no activity on this FD, skip */
-		if (cur_pollfd->revents == 0)
-			continue;
-
-		occurred_events->pos = cur_event->pos;
-		occurred_events->user_data = cur_event->user_data;
-		occurred_events->events = 0;
-
-		if (cur_event->events == WL_LATCH_SET &&
-			(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)
-			{
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events == WL_POSTMASTER_DEATH &&
-				 (cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
-		{
-			/*
-			 * We expect an POLLHUP when the remote end is closed, but because
-			 * we don't expect the pipe to become readable or to have any
-			 * errors either, treat those cases as postmaster death, too.
-			 *
-			 * Be paranoid about a spurious event signaling the postmaster as
-			 * being dead.  There have been reports about that happening with
-			 * older primitives (select(2) to be specific), and a spurious
-			 * WL_POSTMASTER_DEATH event would be painful. Re-checking doesn't
-			 * cost much.
-			 */
-			if (!PostmasterIsAliveInternal())
-			{
-				if (set->exit_on_postmaster_death)
-					proc_exit(1);
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_POSTMASTER_DEATH;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events & (WL_SOCKET_READABLE |
-									  WL_SOCKET_WRITEABLE |
-									  WL_SOCKET_CLOSED))
-		{
-			int			errflags = POLLHUP | POLLERR | POLLNVAL;
-
-			Assert(cur_event->fd >= PGINVALID_SOCKET);
-
-			if ((cur_event->events & WL_SOCKET_READABLE) &&
-				(cur_pollfd->revents & (POLLIN | errflags)))
-			{
-				/* data available in socket, or EOF */
-				occurred_events->events |= WL_SOCKET_READABLE;
-			}
-
-			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
-				(cur_pollfd->revents & (POLLOUT | errflags)))
-			{
-				/* writeable, or EOF */
-				occurred_events->events |= WL_SOCKET_WRITEABLE;
-			}
-
-#ifdef POLLRDHUP
-			if ((cur_event->events & WL_SOCKET_CLOSED) &&
-				(cur_pollfd->revents & (POLLRDHUP | errflags)))
-			{
-				/* remote peer closed, or error */
-				occurred_events->events |= WL_SOCKET_CLOSED;
-			}
-#endif
-
-			if (occurred_events->events != 0)
-			{
-				occurred_events->fd = cur_event->fd;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-	}
-	return returned_events;
-}
-
-#elif defined(WAIT_USE_WIN32)
-
-/*
- * Wait using Windows' WaitForMultipleObjects().  Each call only "consumes" one
- * event, so we keep calling until we've filled up our output buffer to match
- * the behavior of the other implementations.
- *
- * https://blogs.msdn.microsoft.com/oldnewthing/20150409-00/?p=44273
- */
-static inline int
-WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
-					  WaitEvent *occurred_events, int nevents)
-{
-	int			returned_events = 0;
-	DWORD		rc;
-	WaitEvent  *cur_event;
-
-	/* Reset any wait events that need it */
-	for (cur_event = set->events;
-		 cur_event < (set->events + set->nevents);
-		 cur_event++)
-	{
-		if (cur_event->reset)
-		{
-			WaitEventAdjustWin32(set, cur_event);
-			cur_event->reset = false;
-		}
-
-		/*
-		 * We associate the socket with a new event handle for each
-		 * WaitEventSet.  FD_CLOSE is only generated once if the other end
-		 * closes gracefully.  Therefore we might miss the FD_CLOSE
-		 * notification, if it was delivered to another event after we stopped
-		 * waiting for it.  Close that race by peeking for EOF after setting
-		 * up this handle to receive notifications, and before entering the
-		 * sleep.
-		 *
-		 * XXX If we had one event handle for the lifetime of a socket, we
-		 * wouldn't need this.
-		 */
-		if (cur_event->events & WL_SOCKET_READABLE)
-		{
-			char		c;
-			WSABUF		buf;
-			DWORD		received;
-			DWORD		flags;
-
-			buf.buf = &c;
-			buf.len = 1;
-			flags = MSG_PEEK;
-			if (WSARecv(cur_event->fd, &buf, 1, &received, &flags, NULL, NULL) == 0)
-			{
-				occurred_events->pos = cur_event->pos;
-				occurred_events->user_data = cur_event->user_data;
-				occurred_events->events = WL_SOCKET_READABLE;
-				occurred_events->fd = cur_event->fd;
-				return 1;
-			}
-		}
-
-		/*
-		 * Windows does not guarantee to log an FD_WRITE network event
-		 * indicating that more data can be sent unless the previous send()
-		 * failed with WSAEWOULDBLOCK.  While our caller might well have made
-		 * such a call, we cannot assume that here.  Therefore, if waiting for
-		 * write-ready, force the issue by doing a dummy send().  If the dummy
-		 * send() succeeds, assume that the socket is in fact write-ready, and
-		 * return immediately.  Also, if it fails with something other than
-		 * WSAEWOULDBLOCK, return a write-ready indication to let our caller
-		 * deal with the error condition.
-		 */
-		if (cur_event->events & WL_SOCKET_WRITEABLE)
-		{
-			char		c;
-			WSABUF		buf;
-			DWORD		sent;
-			int			r;
-
-			buf.buf = &c;
-			buf.len = 0;
-
-			r = WSASend(cur_event->fd, &buf, 1, &sent, 0, NULL, NULL);
-			if (r == 0 || WSAGetLastError() != WSAEWOULDBLOCK)
-			{
-				occurred_events->pos = cur_event->pos;
-				occurred_events->user_data = cur_event->user_data;
-				occurred_events->events = WL_SOCKET_WRITEABLE;
-				occurred_events->fd = cur_event->fd;
-				return 1;
-			}
-		}
-	}
-
-	/*
-	 * Sleep.
-	 *
-	 * Need to wait for ->nevents + 1, because signal handle is in [0].
-	 */
-	rc = WaitForMultipleObjects(set->nevents + 1, set->handles, FALSE,
-								cur_timeout);
-
-	/* Check return code */
-	if (rc == WAIT_FAILED)
-		elog(ERROR, "WaitForMultipleObjects() failed: error code %lu",
-			 GetLastError());
-	else if (rc == WAIT_TIMEOUT)
-	{
-		/* timeout exceeded */
-		return -1;
-	}
-
-	if (rc == WAIT_OBJECT_0)
-	{
-		/* Service newly-arrived signals */
-		pgwin32_dispatch_queued_signals();
-		return 0;				/* retry */
-	}
-
-	/*
-	 * With an offset of one, due to the always present pgwin32_signal_event,
-	 * the handle offset directly corresponds to a wait event.
-	 */
-	cur_event = (WaitEvent *) &set->events[rc - WAIT_OBJECT_0 - 1];
-
-	for (;;)
-	{
-		int			next_pos;
-		int			count;
-
-		occurred_events->pos = cur_event->pos;
-		occurred_events->user_data = cur_event->user_data;
-		occurred_events->events = 0;
-
-		if (cur_event->events == WL_LATCH_SET)
-		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
-			if (!ResetEvent(set->handles[cur_event->pos + 1]))
-				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
-
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
-			{
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events == WL_POSTMASTER_DEATH)
-		{
-			/*
-			 * Postmaster apparently died.  Since the consequences of falsely
-			 * returning WL_POSTMASTER_DEATH could be pretty unpleasant, we
-			 * take the trouble to positively verify this with
-			 * PostmasterIsAlive(), even though there is no known reason to
-			 * think that the event could be falsely set on Windows.
-			 */
-			if (!PostmasterIsAliveInternal())
-			{
-				if (set->exit_on_postmaster_death)
-					proc_exit(1);
-				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_POSTMASTER_DEATH;
-				occurred_events++;
-				returned_events++;
-			}
-		}
-		else if (cur_event->events & WL_SOCKET_MASK)
-		{
-			WSANETWORKEVENTS resEvents;
-			HANDLE		handle = set->handles[cur_event->pos + 1];
-
-			Assert(cur_event->fd);
-
-			occurred_events->fd = cur_event->fd;
-
-			ZeroMemory(&resEvents, sizeof(resEvents));
-			if (WSAEnumNetworkEvents(cur_event->fd, handle, &resEvents) != 0)
-				elog(ERROR, "failed to enumerate network events: error code %d",
-					 WSAGetLastError());
-			if ((cur_event->events & WL_SOCKET_READABLE) &&
-				(resEvents.lNetworkEvents & FD_READ))
-			{
-				/* data available in socket */
-				occurred_events->events |= WL_SOCKET_READABLE;
-
-				/*------
-				 * WaitForMultipleObjects doesn't guarantee that a read event
-				 * will be returned if the latch 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
-				 * an indefinite hang.
-				 *
-				 * Refer
-				 * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
-				 * for the behavior of socket events.
-				 *------
-				 */
-				cur_event->reset = true;
-			}
-			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
-				(resEvents.lNetworkEvents & FD_WRITE))
-			{
-				/* writeable */
-				occurred_events->events |= WL_SOCKET_WRITEABLE;
-			}
-			if ((cur_event->events & WL_SOCKET_CONNECTED) &&
-				(resEvents.lNetworkEvents & FD_CONNECT))
-			{
-				/* connected */
-				occurred_events->events |= WL_SOCKET_CONNECTED;
-			}
-			if ((cur_event->events & WL_SOCKET_ACCEPT) &&
-				(resEvents.lNetworkEvents & FD_ACCEPT))
-			{
-				/* incoming connection could be accepted */
-				occurred_events->events |= WL_SOCKET_ACCEPT;
-			}
-			if (resEvents.lNetworkEvents & FD_CLOSE)
-			{
-				/* EOF/error, so signal all caller-requested socket flags */
-				occurred_events->events |= (cur_event->events & WL_SOCKET_MASK);
-			}
-
-			if (occurred_events->events != 0)
-			{
-				occurred_events++;
-				returned_events++;
-			}
-		}
-
-		/* Is the output buffer full? */
-		if (returned_events == nevents)
-			break;
-
-		/* Have we run out of possible events? */
-		next_pos = cur_event->pos + 1;
-		if (next_pos == set->nevents)
-			break;
-
-		/*
-		 * Poll the rest of the event handles in the array starting at
-		 * next_pos being careful to skip over the initial signal handle too.
-		 * This time we use a zero timeout.
-		 */
-		count = set->nevents - next_pos;
-		rc = WaitForMultipleObjects(count,
-									set->handles + 1 + next_pos,
-									false,
-									0);
-
-		/*
-		 * We don't distinguish between errors and WAIT_TIMEOUT here because
-		 * we already have events to report.
-		 */
-		if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + count)
-			break;
-
-		/* We have another event to decode. */
-		cur_event = &set->events[next_pos + (rc - WAIT_OBJECT_0)];
-	}
-
-	return returned_events;
-}
-#endif
-
-/*
- * Return whether the current build options can report WL_SOCKET_CLOSED.
- */
-bool
-WaitEventSetCanReportClosed(void)
-{
-#if (defined(WAIT_USE_POLL) && defined(POLLRDHUP)) || \
-	defined(WAIT_USE_EPOLL) || \
-	defined(WAIT_USE_KQUEUE)
-	return true;
-#else
-	return false;
-#endif
-}
-
-/*
- * Get the number of wait events registered in a given WaitEventSet.
- */
-int
-GetNumRegisteredWaitEvents(WaitEventSet *set)
-{
-	return set->nevents;
-}
-
-#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.
- */
-static void
-latch_sigurg_handler(SIGNAL_ARGS)
-{
-	if (waiting)
-		sendSelfPipeByte();
-}
-
-/* Send one byte to the self-pipe, to wake up WaitLatch */
-static void
-sendSelfPipeByte(void)
-{
-	int			rc;
-	char		dummy = 0;
-
-retry:
-	rc = write(selfpipe_writefd, &dummy, 1);
-	if (rc < 0)
-	{
-		/* If interrupted by signal, just retry */
-		if (errno == EINTR)
-			goto retry;
-
-		/*
-		 * If the pipe is full, we don't need to retry, the data that's there
-		 * already is enough to wake up WaitLatch.
-		 */
-		if (errno == EAGAIN || errno == EWOULDBLOCK)
-			return;
-
-		/*
-		 * Oops, the write() failed for some other reason. We might be in a
-		 * signal handler, so it's not safe to elog(). We have no choice but
-		 * silently ignore the error.
-		 */
-		return;
-	}
-}
-
-#endif
-
-#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
-
-/*
- * Read all available data from self-pipe or signalfd.
- *
- * Note: this is only called when waiting = true.  If it fails and doesn't
- * return, it must reset that flag first (though ideally, this will never
- * happen).
- */
-static void
-drain(void)
-{
-	char		buf[1024];
-	int			rc;
-	int			fd;
-
-#ifdef WAIT_USE_SELF_PIPE
-	fd = selfpipe_readfd;
-#else
-	fd = signal_fd;
-#endif
-
-	for (;;)
-	{
-		rc = read(fd, buf, sizeof(buf));
-		if (rc < 0)
-		{
-			if (errno == EAGAIN || errno == EWOULDBLOCK)
-				break;			/* the descriptor is empty */
-			else if (errno == EINTR)
-				continue;		/* retry */
-			else
-			{
-				waiting = false;
-#ifdef WAIT_USE_SELF_PIPE
-				elog(ERROR, "read() on self-pipe failed: %m");
-#else
-				elog(ERROR, "read() on signalfd failed: %m");
-#endif
-			}
-		}
-		else if (rc == 0)
-		{
-			waiting = false;
-#ifdef WAIT_USE_SELF_PIPE
-			elog(ERROR, "unexpected EOF on self-pipe");
-#else
-			elog(ERROR, "unexpected EOF on signalfd");
-#endif
-		}
-		else if (rc < sizeof(buf))
-		{
-			/* we successfully drained the pipe; no need to read() again */
-			break;
-		}
-		/* else buffer wasn't big enough, so read again */
-	}
-}
-
-#endif
-
-static void
-ResOwnerReleaseWaitEventSet(Datum res)
-{
-	WaitEventSet *set = (WaitEventSet *) DatumGetPointer(res);
-
-	Assert(set->owner != NULL);
-	set->owner = NULL;
-	FreeWaitEventSet(set);
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 7473bd1dd73..b1b73dac3be 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -18,5 +18,6 @@ backend_sources += files(
   'sinval.c',
   'sinvaladt.c',
   'standby.c',
+  'waiteventset.c',
 
 )
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
new file mode 100644
index 00000000000..35e836d3398
--- /dev/null
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -0,0 +1,2033 @@
+/*-------------------------------------------------------------------------
+ *
+ * waiteventset.c
+ *	  ppoll()/pselect() like abstraction
+ *
+ * WaitEvents are an abstraction for waiting for one or more events at a time.
+ * The waiting can be done in a race free fashion, similar ppoll() or
+ * 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)
+ * - 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)
+ *
+ * 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 epoll() implementation overcomes the race with a different technique: it
+ * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
+ * don't need to register a signal handler or create our own self-pipe.  We
+ * assume that any system that has Linux epoll() also has Linux signalfd().
+ *
+ * The kqueue() implementation waits for SIGURG with EVFILT_SIGNAL.
+ *
+ * The Windows implementation uses Windows events that are inherited by all
+ * postmaster child processes. There's no need for the self-pipe trick there.
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/ipc/waiteventset.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_EPOLL_H
+#include <sys/epoll.h>
+#endif
+#ifdef HAVE_SYS_EVENT_H
+#include <sys/event.h>
+#endif
+#ifdef HAVE_SYS_SIGNALFD_H
+#include <sys/signalfd.h>
+#endif
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
+
+#include "libpq/pqsignal.h"
+#include "miscadmin.h"
+#include "pgstat.h"
+#include "port/atomics.h"
+#include "portability/instr_time.h"
+#include "postmaster/postmaster.h"
+#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"
+
+/*
+ * Select the fd readiness primitive to use. Normally the "most modern"
+ * primitive supported by the OS will be used, but for testing it can be
+ * useful to manually specify the used primitive.  If desired, just add a
+ * define somewhere before this block.
+ */
+#if defined(WAIT_USE_EPOLL) || defined(WAIT_USE_POLL) || \
+	defined(WAIT_USE_KQUEUE) || defined(WAIT_USE_WIN32)
+/* don't overwrite manual choice */
+#elif defined(HAVE_SYS_EPOLL_H)
+#define WAIT_USE_EPOLL
+#elif defined(HAVE_KQUEUE)
+#define WAIT_USE_KQUEUE
+#elif defined(HAVE_POLL)
+#define WAIT_USE_POLL
+#elif WIN32
+#define WAIT_USE_WIN32
+#else
+#error "no wait set implementation available"
+#endif
+
+/*
+ * By default, we use a self-pipe with poll() and a signalfd with epoll(), if
+ * available.  For testing the choice can also be manually specified.
+ */
+#if defined(WAIT_USE_POLL) || defined(WAIT_USE_EPOLL)
+#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
+/* don't overwrite manual choice */
+#elif defined(WAIT_USE_EPOLL) && defined(HAVE_SYS_SIGNALFD_H)
+#define WAIT_USE_SIGNALFD
+#else
+#define WAIT_USE_SELF_PIPE
+#endif
+#endif
+
+/* typedef in waiteventset.h */
+struct WaitEventSet
+{
+	ResourceOwner owner;
+
+	int			nevents;		/* number of registered events */
+	int			nevents_space;	/* maximum number of events in this set */
+
+	/*
+	 * Array, of nevents_space length, storing the definition of events this
+	 * set is waiting for.
+	 */
+	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.
+	 */
+	Latch	   *latch;
+	int			latch_pos;
+
+	/*
+	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
+	 * is set so that we'll exit immediately if postmaster death is detected,
+	 * instead of returning.
+	 */
+	bool		exit_on_postmaster_death;
+
+#if defined(WAIT_USE_EPOLL)
+	int			epoll_fd;
+	/* epoll_wait returns events in a user provided arrays, allocate once */
+	struct epoll_event *epoll_ret_events;
+#elif defined(WAIT_USE_KQUEUE)
+	int			kqueue_fd;
+	/* kevent returns events in a user provided arrays, allocate once */
+	struct kevent *kqueue_ret_events;
+	bool		report_postmaster_not_running;
+#elif defined(WAIT_USE_POLL)
+	/* poll expects events to be waited on every poll() call, prepare once */
+	struct pollfd *pollfds;
+#elif defined(WAIT_USE_WIN32)
+
+	/*
+	 * Array of windows events. The first element always contains
+	 * pgwin32_signal_event, so the remaining elements are offset by one (i.e.
+	 * event->pos + 1).
+	 */
+	HANDLE	   *handles;
+#endif
+};
+
+#ifndef WIN32
+/* Are we currently in WaitLatch? 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. */
+static int	signal_fd = -1;
+#endif
+
+#ifdef WAIT_USE_SELF_PIPE
+/* Read and write ends of the self-pipe */
+static int	selfpipe_readfd = -1;
+static int	selfpipe_writefd = -1;
+
+/* Process owning the self-pipe --- needed for checking purposes */
+static int	selfpipe_owner_pid = 0;
+
+/* Private function prototypes */
+static void latch_sigurg_handler(SIGNAL_ARGS);
+static void sendSelfPipeByte(void);
+#endif
+
+#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
+static void drain(void);
+#endif
+
+#if defined(WAIT_USE_EPOLL)
+static void WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action);
+#elif defined(WAIT_USE_KQUEUE)
+static void WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events);
+#elif defined(WAIT_USE_POLL)
+static void WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event);
+#elif defined(WAIT_USE_WIN32)
+static void WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event);
+#endif
+
+static inline int WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
+										WaitEvent *occurred_events, int nevents);
+
+/* ResourceOwner support to hold WaitEventSets */
+static void ResOwnerReleaseWaitEventSet(Datum res);
+
+static const ResourceOwnerDesc wait_event_set_resowner_desc =
+{
+	.name = "WaitEventSet",
+	.release_phase = RESOURCE_RELEASE_AFTER_LOCKS,
+	.release_priority = RELEASE_PRIO_WAITEVENTSETS,
+	.ReleaseResource = ResOwnerReleaseWaitEventSet,
+	.DebugPrint = NULL
+};
+
+/* Convenience wrappers over ResourceOwnerRemember/Forget */
+static inline void
+ResourceOwnerRememberWaitEventSet(ResourceOwner owner, WaitEventSet *set)
+{
+	ResourceOwnerRemember(owner, PointerGetDatum(set), &wait_event_set_resowner_desc);
+}
+static inline void
+ResourceOwnerForgetWaitEventSet(ResourceOwner owner, WaitEventSet *set)
+{
+	ResourceOwnerForget(owner, PointerGetDatum(set), &wait_event_set_resowner_desc);
+}
+
+
+/*
+ * 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.
+ */
+void
+InitializeWaitEventSupport(void)
+{
+#if defined(WAIT_USE_SELF_PIPE)
+	int			pipefd[2];
+
+	if (IsUnderPostmaster)
+	{
+		/*
+		 * We might have inherited connections to a self-pipe created by the
+		 * postmaster.  It's critical that child processes create their own
+		 * self-pipes, of course, and we really want them to close the
+		 * inherited FDs for safety's sake.
+		 */
+		if (selfpipe_owner_pid != 0)
+		{
+			/* Assert we go through here but once in a child process */
+			Assert(selfpipe_owner_pid != MyProcPid);
+			/* Release postmaster's pipe FDs; ignore any error */
+			(void) close(selfpipe_readfd);
+			(void) close(selfpipe_writefd);
+			/* Clean up, just for safety's sake; we'll set these below */
+			selfpipe_readfd = selfpipe_writefd = -1;
+			selfpipe_owner_pid = 0;
+			/* Keep fd.c's accounting straight */
+			ReleaseExternalFD();
+			ReleaseExternalFD();
+		}
+		else
+		{
+			/*
+			 * Postmaster didn't create a self-pipe ... or else we're in an
+			 * EXEC_BACKEND build, in which case it doesn't matter since the
+			 * postmaster's pipe FDs were closed by the action of FD_CLOEXEC.
+			 * fd.c won't have state to clean up, either.
+			 */
+			Assert(selfpipe_readfd == -1);
+		}
+	}
+	else
+	{
+		/* In postmaster or standalone backend, assert we do this but once */
+		Assert(selfpipe_readfd == -1);
+		Assert(selfpipe_owner_pid == 0);
+	}
+
+	/*
+	 * 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.
+	 */
+	if (pipe(pipefd) < 0)
+		elog(FATAL, "pipe() failed: %m");
+	if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) == -1)
+		elog(FATAL, "fcntl(F_SETFL) failed on read-end of self-pipe: %m");
+	if (fcntl(pipefd[1], F_SETFL, O_NONBLOCK) == -1)
+		elog(FATAL, "fcntl(F_SETFL) failed on write-end of self-pipe: %m");
+	if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1)
+		elog(FATAL, "fcntl(F_SETFD) failed on read-end of self-pipe: %m");
+	if (fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1)
+		elog(FATAL, "fcntl(F_SETFD) failed on write-end of self-pipe: %m");
+
+	selfpipe_readfd = pipefd[0];
+	selfpipe_writefd = pipefd[1];
+	selfpipe_owner_pid = MyProcPid;
+
+	/* Tell fd.c about these two long-lived FDs */
+	ReserveExternalFD();
+	ReserveExternalFD();
+
+	pqsignal(SIGURG, latch_sigurg_handler);
+#endif
+
+#ifdef WAIT_USE_SIGNALFD
+	sigset_t	signalfd_mask;
+
+	if (IsUnderPostmaster)
+	{
+		/*
+		 * It would probably be safe to re-use the inherited signalfd since
+		 * signalfds only see the current process's pending signals, but it
+		 * seems less surprising to close it and create our own.
+		 */
+		if (signal_fd != -1)
+		{
+			/* Release postmaster's signal FD; ignore any error */
+			(void) close(signal_fd);
+			signal_fd = -1;
+			ReleaseExternalFD();
+		}
+	}
+
+	/* Block SIGURG, because we'll receive it through a signalfd. */
+	sigaddset(&UnBlockSig, SIGURG);
+
+	/* Set up the signalfd to receive SIGURG notifications. */
+	sigemptyset(&signalfd_mask);
+	sigaddset(&signalfd_mask, SIGURG);
+	signal_fd = signalfd(-1, &signalfd_mask, SFD_NONBLOCK | SFD_CLOEXEC);
+	if (signal_fd < 0)
+		elog(FATAL, "signalfd() failed");
+	ReserveExternalFD();
+#endif
+
+#ifdef WAIT_USE_KQUEUE
+	/* Ignore SIGURG, because we'll receive it via kqueue. */
+	pqsignal(SIGURG, SIG_IGN);
+#endif
+}
+
+/*
+ * Create a WaitEventSet with space for nevents different events to wait for.
+ *
+ * These events can then be efficiently waited upon together, using
+ * WaitEventSetWait().
+ *
+ * The WaitEventSet is tracked by the given 'resowner'.  Use NULL for session
+ * lifetime.
+ */
+WaitEventSet *
+CreateWaitEventSet(ResourceOwner resowner, int nevents)
+{
+	WaitEventSet *set;
+	char	   *data;
+	Size		sz = 0;
+
+	/*
+	 * Use MAXALIGN size/alignment to guarantee that later uses of memory are
+	 * aligned correctly. E.g. epoll_event might need 8 byte alignment on some
+	 * platforms, but earlier allocations like WaitEventSet and WaitEvent
+	 * might not be sized to guarantee that when purely using sizeof().
+	 */
+	sz += MAXALIGN(sizeof(WaitEventSet));
+	sz += MAXALIGN(sizeof(WaitEvent) * nevents);
+
+#if defined(WAIT_USE_EPOLL)
+	sz += MAXALIGN(sizeof(struct epoll_event) * nevents);
+#elif defined(WAIT_USE_KQUEUE)
+	sz += MAXALIGN(sizeof(struct kevent) * nevents);
+#elif defined(WAIT_USE_POLL)
+	sz += MAXALIGN(sizeof(struct pollfd) * nevents);
+#elif defined(WAIT_USE_WIN32)
+	/* need space for the pgwin32_signal_event */
+	sz += MAXALIGN(sizeof(HANDLE) * (nevents + 1));
+#endif
+
+	if (resowner != NULL)
+		ResourceOwnerEnlarge(resowner);
+
+	data = (char *) MemoryContextAllocZero(TopMemoryContext, sz);
+
+	set = (WaitEventSet *) data;
+	data += MAXALIGN(sizeof(WaitEventSet));
+
+	set->events = (WaitEvent *) data;
+	data += MAXALIGN(sizeof(WaitEvent) * nevents);
+
+#if defined(WAIT_USE_EPOLL)
+	set->epoll_ret_events = (struct epoll_event *) data;
+	data += MAXALIGN(sizeof(struct epoll_event) * nevents);
+#elif defined(WAIT_USE_KQUEUE)
+	set->kqueue_ret_events = (struct kevent *) data;
+	data += MAXALIGN(sizeof(struct kevent) * nevents);
+#elif defined(WAIT_USE_POLL)
+	set->pollfds = (struct pollfd *) data;
+	data += MAXALIGN(sizeof(struct pollfd) * nevents);
+#elif defined(WAIT_USE_WIN32)
+	set->handles = (HANDLE) data;
+	data += MAXALIGN(sizeof(HANDLE) * nevents);
+#endif
+
+	set->latch = NULL;
+	set->nevents_space = nevents;
+	set->exit_on_postmaster_death = false;
+
+	if (resowner != NULL)
+	{
+		ResourceOwnerRememberWaitEventSet(resowner, set);
+		set->owner = resowner;
+	}
+
+#if defined(WAIT_USE_EPOLL)
+	if (!AcquireExternalFD())
+		elog(ERROR, "AcquireExternalFD, for epoll_create1, failed: %m");
+	set->epoll_fd = epoll_create1(EPOLL_CLOEXEC);
+	if (set->epoll_fd < 0)
+	{
+		ReleaseExternalFD();
+		elog(ERROR, "epoll_create1 failed: %m");
+	}
+#elif defined(WAIT_USE_KQUEUE)
+	if (!AcquireExternalFD())
+		elog(ERROR, "AcquireExternalFD, for kqueue, failed: %m");
+	set->kqueue_fd = kqueue();
+	if (set->kqueue_fd < 0)
+	{
+		ReleaseExternalFD();
+		elog(ERROR, "kqueue failed: %m");
+	}
+	if (fcntl(set->kqueue_fd, F_SETFD, FD_CLOEXEC) == -1)
+	{
+		int			save_errno = errno;
+
+		close(set->kqueue_fd);
+		ReleaseExternalFD();
+		errno = save_errno;
+		elog(ERROR, "fcntl(F_SETFD) failed on kqueue descriptor: %m");
+	}
+	set->report_postmaster_not_running = false;
+#elif defined(WAIT_USE_WIN32)
+
+	/*
+	 * To handle signals while waiting, we need to add a win32 specific event.
+	 * We accounted for the additional event at the top of this routine. See
+	 * port/win32/signal.c for more details.
+	 *
+	 * Note: pgwin32_signal_event should be first to ensure that it will be
+	 * reported when multiple events are set.  We want to guarantee that
+	 * pending signals are serviced.
+	 */
+	set->handles[0] = pgwin32_signal_event;
+	StaticAssertStmt(WSA_INVALID_EVENT == NULL, "");
+#endif
+
+	return set;
+}
+
+/*
+ * Free a previously created WaitEventSet.
+ *
+ * Note: preferably, this shouldn't have to free any resources that could be
+ * inherited across an exec().  If it did, we'd likely leak those resources in
+ * many scenarios.  For the epoll case, we ensure that by setting EPOLL_CLOEXEC
+ * when the FD is created.  For the Windows case, we assume that the handles
+ * involved are non-inheritable.
+ */
+void
+FreeWaitEventSet(WaitEventSet *set)
+{
+	if (set->owner)
+	{
+		ResourceOwnerForgetWaitEventSet(set->owner, set);
+		set->owner = NULL;
+	}
+
+#if defined(WAIT_USE_EPOLL)
+	close(set->epoll_fd);
+	ReleaseExternalFD();
+#elif defined(WAIT_USE_KQUEUE)
+	close(set->kqueue_fd);
+	ReleaseExternalFD();
+#elif defined(WAIT_USE_WIN32)
+	for (WaitEvent *cur_event = set->events;
+		 cur_event < (set->events + set->nevents);
+		 cur_event++)
+	{
+		if (cur_event->events & WL_LATCH_SET)
+		{
+			/* uses the latch's HANDLE */
+		}
+		else if (cur_event->events & WL_POSTMASTER_DEATH)
+		{
+			/* uses PostmasterHandle */
+		}
+		else
+		{
+			/* Clean up the event object we created for the socket */
+			WSAEventSelect(cur_event->fd, NULL, 0);
+			WSACloseEvent(set->handles[cur_event->pos + 1]);
+		}
+	}
+#endif
+
+	pfree(set);
+}
+
+/*
+ * Free a previously created WaitEventSet in a child process after a fork().
+ */
+void
+FreeWaitEventSetAfterFork(WaitEventSet *set)
+{
+#if defined(WAIT_USE_EPOLL)
+	close(set->epoll_fd);
+	ReleaseExternalFD();
+#elif defined(WAIT_USE_KQUEUE)
+	/* kqueues are not normally inherited by child processes */
+	ReleaseExternalFD();
+#endif
+
+	pfree(set);
+}
+
+/* ---
+ * Add an event to the set. Possible events are:
+ * - WL_LATCH_SET: Wait for the latch to be set
+ * - 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
+ * - WL_SOCKET_WRITEABLE: Wait for socket to become writeable,
+ *	 can be combined with other WL_SOCKET_* events
+ * - WL_SOCKET_CONNECTED: Wait for socket connection to be established,
+ *	 can be combined with other WL_SOCKET_* events (on non-Windows
+ *	 platforms, this is the same as WL_SOCKET_WRITEABLE)
+ * - WL_SOCKET_ACCEPT: Wait for new connection to a server socket,
+ *	 can be combined with other WL_SOCKET_* events (on non-Windows
+ *	 platforms, this is the same as WL_SOCKET_READABLE)
+ * - WL_SOCKET_CLOSED: Wait for socket to be closed by remote peer.
+ * - WL_EXIT_ON_PM_DEATH: Exit immediately if the postmaster dies
+ *
+ * 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.
+ *
+ * The user_data pointer specified here will be set for the events returned
+ * by WaitEventSetWait(), allowing to easily associate additional data with
+ * events.
+ */
+int
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+				  void *user_data)
+{
+	WaitEvent  *event;
+
+	/* not enough space */
+	Assert(set->nevents < set->nevents_space);
+
+	if (events == WL_EXIT_ON_PM_DEATH)
+	{
+		events = WL_POSTMASTER_DEATH;
+		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
+	{
+		if (events & WL_LATCH_SET)
+			elog(ERROR, "cannot wait on latch without a specified latch");
+	}
+
+	/* waiting for socket readiness without a socket indicates a bug */
+	if (fd == PGINVALID_SOCKET && (events & WL_SOCKET_MASK))
+		elog(ERROR, "cannot wait on socket event without a socket");
+
+	event = &set->events[set->nevents];
+	event->pos = set->nevents++;
+	event->fd = fd;
+	event->events = events;
+	event->user_data = user_data;
+#ifdef WIN32
+	event->reset = false;
+#endif
+
+	if (events == WL_LATCH_SET)
+	{
+		set->latch = latch;
+		set->latch_pos = event->pos;
+#if defined(WAIT_USE_SELF_PIPE)
+		event->fd = selfpipe_readfd;
+#elif defined(WAIT_USE_SIGNALFD)
+		event->fd = signal_fd;
+#else
+		event->fd = PGINVALID_SOCKET;
+#ifdef WAIT_USE_EPOLL
+		return event->pos;
+#endif
+#endif
+	}
+	else if (events == WL_POSTMASTER_DEATH)
+	{
+#ifndef WIN32
+		event->fd = postmaster_alive_fds[POSTMASTER_FD_WATCH];
+#endif
+	}
+
+	/* perform wait primitive specific initialization, if needed */
+#if defined(WAIT_USE_EPOLL)
+	WaitEventAdjustEpoll(set, event, EPOLL_CTL_ADD);
+#elif defined(WAIT_USE_KQUEUE)
+	WaitEventAdjustKqueue(set, event, 0);
+#elif defined(WAIT_USE_POLL)
+	WaitEventAdjustPoll(set, event);
+#elif defined(WAIT_USE_WIN32)
+	WaitEventAdjustWin32(set, event);
+#endif
+
+	return event->pos;
+}
+
+/*
+ * 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.
+ *
+ * 'pos' is the id returned by AddWaitEventToSet.
+ */
+void
+ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
+{
+	WaitEvent  *event;
+#if defined(WAIT_USE_KQUEUE)
+	int			old_events;
+#endif
+
+	Assert(pos < set->nevents);
+
+	event = &set->events[pos];
+#if defined(WAIT_USE_KQUEUE)
+	old_events = event->events;
+#endif
+
+	/*
+	 * If neither the event mask nor the associated latch 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))
+		return;
+
+	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
+	if (event->events & WL_POSTMASTER_DEATH)
+	{
+		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
+			elog(ERROR, "cannot modify postmaster death event");
+		set->exit_on_postmaster_death =	((events & WL_EXIT_ON_PM_DEATH) != 0);
+		return;
+	}
+
+	if (event->events & WL_LATCH_SET &&
+		events != event->events)
+	{
+		elog(ERROR, "cannot modify latch event");
+	}
+
+	/* FIXME: validate event mask */
+	event->events = events;
+
+	if (events == WL_LATCH_SET)
+	{
+		if (latch && latch->owner_pid != MyProcPid)
+			elog(ERROR, "cannot wait on a latch owned by another process");
+		set->latch = latch;
+
+		/*
+		 * 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.
+		 */
+#if defined(WAIT_USE_WIN32)
+		if (!latch)
+			return;
+#else
+		return;
+#endif
+	}
+
+#if defined(WAIT_USE_EPOLL)
+	WaitEventAdjustEpoll(set, event, EPOLL_CTL_MOD);
+#elif defined(WAIT_USE_KQUEUE)
+	WaitEventAdjustKqueue(set, event, old_events);
+#elif defined(WAIT_USE_POLL)
+	WaitEventAdjustPoll(set, event);
+#elif defined(WAIT_USE_WIN32)
+	WaitEventAdjustWin32(set, event);
+#endif
+}
+
+#if defined(WAIT_USE_EPOLL)
+/*
+ * action can be one of EPOLL_CTL_ADD | EPOLL_CTL_MOD | EPOLL_CTL_DEL
+ */
+static void
+WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
+{
+	struct epoll_event epoll_ev;
+	int			rc;
+
+	/* pointer to our event, returned by epoll_wait */
+	epoll_ev.data.ptr = event;
+	/* always wait for errors */
+	epoll_ev.events = EPOLLERR | EPOLLHUP;
+
+	/* prepare pollfd entry once */
+	if (event->events == WL_LATCH_SET)
+	{
+		Assert(set->latch != NULL);
+		epoll_ev.events |= EPOLLIN;
+	}
+	else if (event->events == WL_POSTMASTER_DEATH)
+	{
+		epoll_ev.events |= EPOLLIN;
+	}
+	else
+	{
+		Assert(event->fd != PGINVALID_SOCKET);
+		Assert(event->events & (WL_SOCKET_READABLE |
+								WL_SOCKET_WRITEABLE |
+								WL_SOCKET_CLOSED));
+
+		if (event->events & WL_SOCKET_READABLE)
+			epoll_ev.events |= EPOLLIN;
+		if (event->events & WL_SOCKET_WRITEABLE)
+			epoll_ev.events |= EPOLLOUT;
+		if (event->events & WL_SOCKET_CLOSED)
+			epoll_ev.events |= EPOLLRDHUP;
+	}
+
+	/*
+	 * Even though unused, we also pass epoll_ev as the data argument if
+	 * EPOLL_CTL_DEL is passed as action.  There used to be an epoll bug
+	 * requiring that, and actually it makes the code simpler...
+	 */
+	rc = epoll_ctl(set->epoll_fd, action, event->fd, &epoll_ev);
+
+	if (rc < 0)
+		ereport(ERROR,
+				(errcode_for_socket_access(),
+				 errmsg("%s() failed: %m",
+						"epoll_ctl")));
+}
+#endif
+
+#if defined(WAIT_USE_POLL)
+static void
+WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
+{
+	struct pollfd *pollfd = &set->pollfds[event->pos];
+
+	pollfd->revents = 0;
+	pollfd->fd = event->fd;
+
+	/* prepare pollfd entry once */
+	if (event->events == WL_LATCH_SET)
+	{
+		Assert(set->latch != NULL);
+		pollfd->events = POLLIN;
+	}
+	else if (event->events == WL_POSTMASTER_DEATH)
+	{
+		pollfd->events = POLLIN;
+	}
+	else
+	{
+		Assert(event->events & (WL_SOCKET_READABLE |
+								WL_SOCKET_WRITEABLE |
+								WL_SOCKET_CLOSED));
+		pollfd->events = 0;
+		if (event->events & WL_SOCKET_READABLE)
+			pollfd->events |= POLLIN;
+		if (event->events & WL_SOCKET_WRITEABLE)
+			pollfd->events |= POLLOUT;
+#ifdef POLLRDHUP
+		if (event->events & WL_SOCKET_CLOSED)
+			pollfd->events |= POLLRDHUP;
+#endif
+	}
+
+	Assert(event->fd != PGINVALID_SOCKET);
+}
+#endif
+
+#if defined(WAIT_USE_KQUEUE)
+
+/*
+ * On most BSD family systems, the udata member of struct kevent is of type
+ * void *, so we could directly convert to/from WaitEvent *.  Unfortunately,
+ * NetBSD has it as intptr_t, so here we wallpaper over that difference with
+ * an lvalue cast.
+ */
+#define AccessWaitEvent(k_ev) (*((WaitEvent **)(&(k_ev)->udata)))
+
+static inline void
+WaitEventAdjustKqueueAdd(struct kevent *k_ev, int filter, int action,
+						 WaitEvent *event)
+{
+	k_ev->ident = event->fd;
+	k_ev->filter = filter;
+	k_ev->flags = action;
+	k_ev->fflags = 0;
+	k_ev->data = 0;
+	AccessWaitEvent(k_ev) = event;
+}
+
+static inline void
+WaitEventAdjustKqueueAddPostmaster(struct kevent *k_ev, WaitEvent *event)
+{
+	/* For now postmaster death can only be added, not removed. */
+	k_ev->ident = PostmasterPid;
+	k_ev->filter = EVFILT_PROC;
+	k_ev->flags = EV_ADD;
+	k_ev->fflags = NOTE_EXIT;
+	k_ev->data = 0;
+	AccessWaitEvent(k_ev) = event;
+}
+
+static inline void
+WaitEventAdjustKqueueAddLatch(struct kevent *k_ev, WaitEvent *event)
+{
+	/* For now latch can only be added, not removed. */
+	k_ev->ident = SIGURG;
+	k_ev->filter = EVFILT_SIGNAL;
+	k_ev->flags = EV_ADD;
+	k_ev->fflags = 0;
+	k_ev->data = 0;
+	AccessWaitEvent(k_ev) = event;
+}
+
+/*
+ * old_events is the previous event mask, used to compute what has changed.
+ */
+static void
+WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
+{
+	int			rc;
+	struct kevent k_ev[2];
+	int			count = 0;
+	bool		new_filt_read = false;
+	bool		old_filt_read = false;
+	bool		new_filt_write = false;
+	bool		old_filt_write = false;
+
+	if (old_events == event->events)
+		return;
+
+	Assert(event->events != WL_LATCH_SET || set->latch != NULL);
+	Assert(event->events == WL_LATCH_SET ||
+		   event->events == WL_POSTMASTER_DEATH ||
+		   (event->events & (WL_SOCKET_READABLE |
+							 WL_SOCKET_WRITEABLE |
+							 WL_SOCKET_CLOSED)));
+
+	if (event->events == WL_POSTMASTER_DEATH)
+	{
+		/*
+		 * Unlike all the other implementations, we detect postmaster death
+		 * using process notification instead of waiting on the postmaster
+		 * alive pipe.
+		 */
+		WaitEventAdjustKqueueAddPostmaster(&k_ev[count++], event);
+	}
+	else if (event->events == WL_LATCH_SET)
+	{
+		/* We detect latch wakeup using a signal event. */
+		WaitEventAdjustKqueueAddLatch(&k_ev[count++], event);
+	}
+	else
+	{
+		/*
+		 * We need to compute the adds and deletes required to get from the
+		 * old event mask to the new event mask, since kevent treats readable
+		 * and writable as separate events.
+		 */
+		if (old_events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
+			old_filt_read = true;
+		if (event->events & (WL_SOCKET_READABLE | WL_SOCKET_CLOSED))
+			new_filt_read = true;
+		if (old_events & WL_SOCKET_WRITEABLE)
+			old_filt_write = true;
+		if (event->events & WL_SOCKET_WRITEABLE)
+			new_filt_write = true;
+		if (old_filt_read && !new_filt_read)
+			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_READ, EV_DELETE,
+									 event);
+		else if (!old_filt_read && new_filt_read)
+			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_READ, EV_ADD,
+									 event);
+		if (old_filt_write && !new_filt_write)
+			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_WRITE, EV_DELETE,
+									 event);
+		else if (!old_filt_write && new_filt_write)
+			WaitEventAdjustKqueueAdd(&k_ev[count++], EVFILT_WRITE, EV_ADD,
+									 event);
+	}
+
+	/* For WL_SOCKET_READ -> WL_SOCKET_CLOSED, no change needed. */
+	if (count == 0)
+		return;
+
+	Assert(count <= 2);
+
+	rc = kevent(set->kqueue_fd, &k_ev[0], count, NULL, 0, NULL);
+
+	/*
+	 * When adding the postmaster's pid, we have to consider that it might
+	 * already have exited and perhaps even been replaced by another process
+	 * with the same pid.  If so, we have to defer reporting this as an event
+	 * until the next call to WaitEventSetWaitBlock().
+	 */
+
+	if (rc < 0)
+	{
+		if (event->events == WL_POSTMASTER_DEATH &&
+			(errno == ESRCH || errno == EACCES))
+			set->report_postmaster_not_running = true;
+		else
+			ereport(ERROR,
+					(errcode_for_socket_access(),
+					 errmsg("%s() failed: %m",
+							"kevent")));
+	}
+	else if (event->events == WL_POSTMASTER_DEATH &&
+			 PostmasterPid != getppid() &&
+			 !PostmasterIsAlive())
+	{
+		/*
+		 * The extra PostmasterIsAliveInternal() check prevents false alarms
+		 * on systems that give a different value for getppid() while being
+		 * traced by a debugger.
+		 */
+		set->report_postmaster_not_running = true;
+	}
+}
+
+#endif
+
+#if defined(WAIT_USE_WIN32)
+static void
+WaitEventAdjustWin32(WaitEventSet *set, WaitEvent *event)
+{
+	HANDLE	   *handle = &set->handles[event->pos + 1];
+
+	if (event->events == WL_LATCH_SET)
+	{
+		Assert(set->latch != NULL);
+		*handle = set->latch->event;
+	}
+	else if (event->events == WL_POSTMASTER_DEATH)
+	{
+		*handle = PostmasterHandle;
+	}
+	else
+	{
+		int			flags = FD_CLOSE;	/* always check for errors/EOF */
+
+		if (event->events & WL_SOCKET_READABLE)
+			flags |= FD_READ;
+		if (event->events & WL_SOCKET_WRITEABLE)
+			flags |= FD_WRITE;
+		if (event->events & WL_SOCKET_CONNECTED)
+			flags |= FD_CONNECT;
+		if (event->events & WL_SOCKET_ACCEPT)
+			flags |= FD_ACCEPT;
+
+		if (*handle == WSA_INVALID_EVENT)
+		{
+			*handle = WSACreateEvent();
+			if (*handle == WSA_INVALID_EVENT)
+				elog(ERROR, "failed to create event for socket: error code %d",
+					 WSAGetLastError());
+		}
+		if (WSAEventSelect(event->fd, *handle, flags) != 0)
+			elog(ERROR, "failed to set up event for socket: error code %d",
+				 WSAGetLastError());
+
+		Assert(event->fd != PGINVALID_SOCKET);
+	}
+}
+#endif
+
+/*
+ * Wait for events added to the set to happen, or until the timeout is
+ * reached.  At most nevents occurred events are returned.
+ *
+ * If timeout = -1, block until an event occurs; if 0, check sockets for
+ * readiness, but don't block; if > 0, block for at most timeout milliseconds.
+ *
+ * Returns the number of events occurred, or 0 if the timeout was reached.
+ *
+ * Returned events will have the fd, pos, user_data fields set to the
+ * values associated with the registered event.
+ */
+int
+WaitEventSetWait(WaitEventSet *set, long timeout,
+				 WaitEvent *occurred_events, int nevents,
+				 uint32 wait_event_info)
+{
+	int			returned_events = 0;
+	instr_time	start_time;
+	instr_time	cur_time;
+	long		cur_timeout = -1;
+
+	Assert(nevents > 0);
+
+	/*
+	 * Initialize timeout if requested.  We must record the current time so
+	 * that we can determine the remaining timeout if interrupted.
+	 */
+	if (timeout >= 0)
+	{
+		INSTR_TIME_SET_CURRENT(start_time);
+		Assert(timeout >= 0 && timeout <= INT_MAX);
+		cur_timeout = timeout;
+	}
+	else
+		INSTR_TIME_SET_ZERO(start_time);
+
+	pgstat_report_wait_start(wait_event_info);
+
+#ifndef WIN32
+	waiting = true;
+#else
+	/* Ensure that signals are serviced even if latch is already set */
+	pgwin32_dispatch_queued_signals();
+#endif
+	while (returned_events == 0)
+	{
+		int			rc;
+
+		/*
+		 * 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.
+		 */
+		if (set->latch && !set->latch->is_set)
+		{
+			/* about to sleep on a latch */
+			set->latch->maybe_sleeping = true;
+			pg_memory_barrier();
+			/* and recheck */
+		}
+
+		if (set->latch && set->latch->is_set)
+		{
+			occurred_events->fd = PGINVALID_SOCKET;
+			occurred_events->pos = set->latch_pos;
+			occurred_events->user_data =
+				set->events[set->latch_pos].user_data;
+			occurred_events->events = WL_LATCH_SET;
+			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.
+			 */
+			cur_timeout = 0;
+			timeout = 0;
+		}
+
+		/*
+		 * 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.
+		 */
+		rc = WaitEventSetWaitBlock(set, cur_timeout,
+								   occurred_events, nevents - returned_events);
+
+		if (set->latch &&
+			set->latch->maybe_sleeping)
+			set->latch->maybe_sleeping = false;
+
+		if (rc == -1)
+			break;				/* timeout occurred */
+		else
+			returned_events += rc;
+
+		/* If we're not done, update cur_timeout for next iteration */
+		if (returned_events == 0 && timeout >= 0)
+		{
+			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
+	waiting = false;
+#endif
+
+	pgstat_report_wait_end();
+
+	return returned_events;
+}
+
+
+#if defined(WAIT_USE_EPOLL)
+
+/*
+ * Wait using linux's epoll_wait(2).
+ *
+ * This is the preferable wait method, as several readiness notifications are
+ * delivered, without having to iterate through all of set->events. The return
+ * epoll_event struct contain a pointer to our events, making association
+ * easy.
+ */
+static inline int
+WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
+					  WaitEvent *occurred_events, int nevents)
+{
+	int			returned_events = 0;
+	int			rc;
+	WaitEvent  *cur_event;
+	struct epoll_event *cur_epoll_event;
+
+	/* Sleep */
+	rc = epoll_wait(set->epoll_fd, set->epoll_ret_events,
+					Min(nevents, set->nevents_space), cur_timeout);
+
+	/* Check return code */
+	if (rc < 0)
+	{
+		/* EINTR is okay, otherwise complain */
+		if (errno != EINTR)
+		{
+			waiting = false;
+			ereport(ERROR,
+					(errcode_for_socket_access(),
+					 errmsg("%s() failed: %m",
+							"epoll_wait")));
+		}
+		return 0;
+	}
+	else if (rc == 0)
+	{
+		/* timeout exceeded */
+		return -1;
+	}
+
+	/*
+	 * At least one event occurred, iterate over the returned epoll events
+	 * until they're either all processed, or we've returned all the events
+	 * the caller desired.
+	 */
+	for (cur_epoll_event = set->epoll_ret_events;
+		 cur_epoll_event < (set->epoll_ret_events + rc) &&
+		 returned_events < nevents;
+		 cur_epoll_event++)
+	{
+		/* epoll's data pointer is set to the associated WaitEvent */
+		cur_event = (WaitEvent *) cur_epoll_event->data.ptr;
+
+		occurred_events->pos = cur_event->pos;
+		occurred_events->user_data = cur_event->user_data;
+		occurred_events->events = 0;
+
+		if (cur_event->events == WL_LATCH_SET &&
+			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
+		{
+			/* Drain the signalfd. */
+			drain();
+
+			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			{
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_LATCH_SET;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events == WL_POSTMASTER_DEATH &&
+				 cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
+		{
+			/*
+			 * We expect an EPOLLHUP when the remote end is closed, but
+			 * because we don't expect the pipe to become readable or to have
+			 * any errors either, treat those cases as postmaster death, too.
+			 *
+			 * Be paranoid about a spurious event signaling the postmaster as
+			 * being dead.  There have been reports about that happening with
+			 * older primitives (select(2) to be specific), and a spurious
+			 * WL_POSTMASTER_DEATH event would be painful. Re-checking doesn't
+			 * cost much.
+			 */
+			if (!PostmasterIsAliveInternal())
+			{
+				if (set->exit_on_postmaster_death)
+					proc_exit(1);
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_POSTMASTER_DEATH;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events & (WL_SOCKET_READABLE |
+									  WL_SOCKET_WRITEABLE |
+									  WL_SOCKET_CLOSED))
+		{
+			Assert(cur_event->fd != PGINVALID_SOCKET);
+
+			if ((cur_event->events & WL_SOCKET_READABLE) &&
+				(cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP)))
+			{
+				/* data available in socket, or EOF */
+				occurred_events->events |= WL_SOCKET_READABLE;
+			}
+
+			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
+				(cur_epoll_event->events & (EPOLLOUT | EPOLLERR | EPOLLHUP)))
+			{
+				/* writable, or EOF */
+				occurred_events->events |= WL_SOCKET_WRITEABLE;
+			}
+
+			if ((cur_event->events & WL_SOCKET_CLOSED) &&
+				(cur_epoll_event->events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP)))
+			{
+				/* remote peer shut down, or error */
+				occurred_events->events |= WL_SOCKET_CLOSED;
+			}
+
+			if (occurred_events->events != 0)
+			{
+				occurred_events->fd = cur_event->fd;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+	}
+
+	return returned_events;
+}
+
+#elif defined(WAIT_USE_KQUEUE)
+
+/*
+ * Wait using kevent(2) on BSD-family systems and macOS.
+ *
+ * For now this mirrors the epoll code, but in future it could modify the fd
+ * set in the same call to kevent as it uses for waiting instead of doing that
+ * with separate system calls.
+ */
+static int
+WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
+					  WaitEvent *occurred_events, int nevents)
+{
+	int			returned_events = 0;
+	int			rc;
+	WaitEvent  *cur_event;
+	struct kevent *cur_kqueue_event;
+	struct timespec timeout;
+	struct timespec *timeout_p;
+
+	if (cur_timeout < 0)
+		timeout_p = NULL;
+	else
+	{
+		timeout.tv_sec = cur_timeout / 1000;
+		timeout.tv_nsec = (cur_timeout % 1000) * 1000000;
+		timeout_p = &timeout;
+	}
+
+	/*
+	 * Report postmaster events discovered by WaitEventAdjustKqueue() or an
+	 * earlier call to WaitEventSetWait().
+	 */
+	if (unlikely(set->report_postmaster_not_running))
+	{
+		if (set->exit_on_postmaster_death)
+			proc_exit(1);
+		occurred_events->fd = PGINVALID_SOCKET;
+		occurred_events->events = WL_POSTMASTER_DEATH;
+		return 1;
+	}
+
+	/* Sleep */
+	rc = kevent(set->kqueue_fd, NULL, 0,
+				set->kqueue_ret_events,
+				Min(nevents, set->nevents_space),
+				timeout_p);
+
+	/* Check return code */
+	if (rc < 0)
+	{
+		/* EINTR is okay, otherwise complain */
+		if (errno != EINTR)
+		{
+			waiting = false;
+			ereport(ERROR,
+					(errcode_for_socket_access(),
+					 errmsg("%s() failed: %m",
+							"kevent")));
+		}
+		return 0;
+	}
+	else if (rc == 0)
+	{
+		/* timeout exceeded */
+		return -1;
+	}
+
+	/*
+	 * At least one event occurred, iterate over the returned kqueue events
+	 * until they're either all processed, or we've returned all the events
+	 * the caller desired.
+	 */
+	for (cur_kqueue_event = set->kqueue_ret_events;
+		 cur_kqueue_event < (set->kqueue_ret_events + rc) &&
+		 returned_events < nevents;
+		 cur_kqueue_event++)
+	{
+		/* kevent's udata points to the associated WaitEvent */
+		cur_event = AccessWaitEvent(cur_kqueue_event);
+
+		occurred_events->pos = cur_event->pos;
+		occurred_events->user_data = cur_event->user_data;
+		occurred_events->events = 0;
+
+		if (cur_event->events == WL_LATCH_SET &&
+			cur_kqueue_event->filter == EVFILT_SIGNAL)
+		{
+			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			{
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_LATCH_SET;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events == WL_POSTMASTER_DEATH &&
+				 cur_kqueue_event->filter == EVFILT_PROC &&
+				 (cur_kqueue_event->fflags & NOTE_EXIT) != 0)
+		{
+			/*
+			 * The kernel will tell this kqueue object only once about the
+			 * exit of the postmaster, so let's remember that for next time so
+			 * that we provide level-triggered semantics.
+			 */
+			set->report_postmaster_not_running = true;
+
+			if (set->exit_on_postmaster_death)
+				proc_exit(1);
+			occurred_events->fd = PGINVALID_SOCKET;
+			occurred_events->events = WL_POSTMASTER_DEATH;
+			occurred_events++;
+			returned_events++;
+		}
+		else if (cur_event->events & (WL_SOCKET_READABLE |
+									  WL_SOCKET_WRITEABLE |
+									  WL_SOCKET_CLOSED))
+		{
+			Assert(cur_event->fd >= 0);
+
+			if ((cur_event->events & WL_SOCKET_READABLE) &&
+				(cur_kqueue_event->filter == EVFILT_READ))
+			{
+				/* readable, or EOF */
+				occurred_events->events |= WL_SOCKET_READABLE;
+			}
+
+			if ((cur_event->events & WL_SOCKET_CLOSED) &&
+				(cur_kqueue_event->filter == EVFILT_READ) &&
+				(cur_kqueue_event->flags & EV_EOF))
+			{
+				/* the remote peer has shut down */
+				occurred_events->events |= WL_SOCKET_CLOSED;
+			}
+
+			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
+				(cur_kqueue_event->filter == EVFILT_WRITE))
+			{
+				/* writable, or EOF */
+				occurred_events->events |= WL_SOCKET_WRITEABLE;
+			}
+
+			if (occurred_events->events != 0)
+			{
+				occurred_events->fd = cur_event->fd;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+	}
+
+	return returned_events;
+}
+
+#elif defined(WAIT_USE_POLL)
+
+/*
+ * Wait using poll(2).
+ *
+ * This allows to receive readiness notifications for several events at once,
+ * but requires iterating through all of set->pollfds.
+ */
+static inline int
+WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
+					  WaitEvent *occurred_events, int nevents)
+{
+	int			returned_events = 0;
+	int			rc;
+	WaitEvent  *cur_event;
+	struct pollfd *cur_pollfd;
+
+	/* Sleep */
+	rc = poll(set->pollfds, set->nevents, (int) cur_timeout);
+
+	/* Check return code */
+	if (rc < 0)
+	{
+		/* EINTR is okay, otherwise complain */
+		if (errno != EINTR)
+		{
+			waiting = false;
+			ereport(ERROR,
+					(errcode_for_socket_access(),
+					 errmsg("%s() failed: %m",
+							"poll")));
+		}
+		return 0;
+	}
+	else if (rc == 0)
+	{
+		/* timeout exceeded */
+		return -1;
+	}
+
+	for (cur_event = set->events, cur_pollfd = set->pollfds;
+		 cur_event < (set->events + set->nevents) &&
+		 returned_events < nevents;
+		 cur_event++, cur_pollfd++)
+	{
+		/* no activity on this FD, skip */
+		if (cur_pollfd->revents == 0)
+			continue;
+
+		occurred_events->pos = cur_event->pos;
+		occurred_events->user_data = cur_event->user_data;
+		occurred_events->events = 0;
+
+		if (cur_event->events == WL_LATCH_SET &&
+			(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)
+			{
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_LATCH_SET;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events == WL_POSTMASTER_DEATH &&
+				 (cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
+		{
+			/*
+			 * We expect an POLLHUP when the remote end is closed, but because
+			 * we don't expect the pipe to become readable or to have any
+			 * errors either, treat those cases as postmaster death, too.
+			 *
+			 * Be paranoid about a spurious event signaling the postmaster as
+			 * being dead.  There have been reports about that happening with
+			 * older primitives (select(2) to be specific), and a spurious
+			 * WL_POSTMASTER_DEATH event would be painful. Re-checking doesn't
+			 * cost much.
+			 */
+			if (!PostmasterIsAliveInternal())
+			{
+				if (set->exit_on_postmaster_death)
+					proc_exit(1);
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_POSTMASTER_DEATH;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events & (WL_SOCKET_READABLE |
+									  WL_SOCKET_WRITEABLE |
+									  WL_SOCKET_CLOSED))
+		{
+			int			errflags = POLLHUP | POLLERR | POLLNVAL;
+
+			Assert(cur_event->fd >= PGINVALID_SOCKET);
+
+			if ((cur_event->events & WL_SOCKET_READABLE) &&
+				(cur_pollfd->revents & (POLLIN | errflags)))
+			{
+				/* data available in socket, or EOF */
+				occurred_events->events |= WL_SOCKET_READABLE;
+			}
+
+			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
+				(cur_pollfd->revents & (POLLOUT | errflags)))
+			{
+				/* writeable, or EOF */
+				occurred_events->events |= WL_SOCKET_WRITEABLE;
+			}
+
+#ifdef POLLRDHUP
+			if ((cur_event->events & WL_SOCKET_CLOSED) &&
+				(cur_pollfd->revents & (POLLRDHUP | errflags)))
+			{
+				/* remote peer closed, or error */
+				occurred_events->events |= WL_SOCKET_CLOSED;
+			}
+#endif
+
+			if (occurred_events->events != 0)
+			{
+				occurred_events->fd = cur_event->fd;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+	}
+	return returned_events;
+}
+
+#elif defined(WAIT_USE_WIN32)
+
+/*
+ * Wait using Windows' WaitForMultipleObjects().  Each call only "consumes" one
+ * event, so we keep calling until we've filled up our output buffer to match
+ * the behavior of the other implementations.
+ *
+ * https://blogs.msdn.microsoft.com/oldnewthing/20150409-00/?p=44273
+ */
+static inline int
+WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
+					  WaitEvent *occurred_events, int nevents)
+{
+	int			returned_events = 0;
+	DWORD		rc;
+	WaitEvent  *cur_event;
+
+	/* Reset any wait events that need it */
+	for (cur_event = set->events;
+		 cur_event < (set->events + set->nevents);
+		 cur_event++)
+	{
+		if (cur_event->reset)
+		{
+			WaitEventAdjustWin32(set, cur_event);
+			cur_event->reset = false;
+		}
+
+		/*
+		 * We associate the socket with a new event handle for each
+		 * WaitEventSet.  FD_CLOSE is only generated once if the other end
+		 * closes gracefully.  Therefore we might miss the FD_CLOSE
+		 * notification, if it was delivered to another event after we stopped
+		 * waiting for it.  Close that race by peeking for EOF after setting
+		 * up this handle to receive notifications, and before entering the
+		 * sleep.
+		 *
+		 * XXX If we had one event handle for the lifetime of a socket, we
+		 * wouldn't need this.
+		 */
+		if (cur_event->events & WL_SOCKET_READABLE)
+		{
+			char		c;
+			WSABUF		buf;
+			DWORD		received;
+			DWORD		flags;
+
+			buf.buf = &c;
+			buf.len = 1;
+			flags = MSG_PEEK;
+			if (WSARecv(cur_event->fd, &buf, 1, &received, &flags, NULL, NULL) == 0)
+			{
+				occurred_events->pos = cur_event->pos;
+				occurred_events->user_data = cur_event->user_data;
+				occurred_events->events = WL_SOCKET_READABLE;
+				occurred_events->fd = cur_event->fd;
+				return 1;
+			}
+		}
+
+		/*
+		 * Windows does not guarantee to log an FD_WRITE network event
+		 * indicating that more data can be sent unless the previous send()
+		 * failed with WSAEWOULDBLOCK.  While our caller might well have made
+		 * such a call, we cannot assume that here.  Therefore, if waiting for
+		 * write-ready, force the issue by doing a dummy send().  If the dummy
+		 * send() succeeds, assume that the socket is in fact write-ready, and
+		 * return immediately.  Also, if it fails with something other than
+		 * WSAEWOULDBLOCK, return a write-ready indication to let our caller
+		 * deal with the error condition.
+		 */
+		if (cur_event->events & WL_SOCKET_WRITEABLE)
+		{
+			char		c;
+			WSABUF		buf;
+			DWORD		sent;
+			int			r;
+
+			buf.buf = &c;
+			buf.len = 0;
+
+			r = WSASend(cur_event->fd, &buf, 1, &sent, 0, NULL, NULL);
+			if (r == 0 || WSAGetLastError() != WSAEWOULDBLOCK)
+			{
+				occurred_events->pos = cur_event->pos;
+				occurred_events->user_data = cur_event->user_data;
+				occurred_events->events = WL_SOCKET_WRITEABLE;
+				occurred_events->fd = cur_event->fd;
+				return 1;
+			}
+		}
+	}
+
+	/*
+	 * Sleep.
+	 *
+	 * Need to wait for ->nevents + 1, because signal handle is in [0].
+	 */
+	rc = WaitForMultipleObjects(set->nevents + 1, set->handles, FALSE,
+								cur_timeout);
+
+	/* Check return code */
+	if (rc == WAIT_FAILED)
+		elog(ERROR, "WaitForMultipleObjects() failed: error code %lu",
+			 GetLastError());
+	else if (rc == WAIT_TIMEOUT)
+	{
+		/* timeout exceeded */
+		return -1;
+	}
+
+	if (rc == WAIT_OBJECT_0)
+	{
+		/* Service newly-arrived signals */
+		pgwin32_dispatch_queued_signals();
+		return 0;				/* retry */
+	}
+
+	/*
+	 * With an offset of one, due to the always present pgwin32_signal_event,
+	 * the handle offset directly corresponds to a wait event.
+	 */
+	cur_event = (WaitEvent *) &set->events[rc - WAIT_OBJECT_0 - 1];
+
+	for (;;)
+	{
+		int			next_pos;
+		int			count;
+
+		occurred_events->pos = cur_event->pos;
+		occurred_events->user_data = cur_event->user_data;
+		occurred_events->events = 0;
+
+		if (cur_event->events == WL_LATCH_SET)
+		{
+			/*
+			 * We cannot use set->latch->event to reset the fired event if we
+			 * aren't waiting on this latch now.
+			 */
+			if (!ResetEvent(set->handles[cur_event->pos + 1]))
+				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
+
+			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			{
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_LATCH_SET;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events == WL_POSTMASTER_DEATH)
+		{
+			/*
+			 * Postmaster apparently died.  Since the consequences of falsely
+			 * returning WL_POSTMASTER_DEATH could be pretty unpleasant, we
+			 * take the trouble to positively verify this with
+			 * PostmasterIsAlive(), even though there is no known reason to
+			 * think that the event could be falsely set on Windows.
+			 */
+			if (!PostmasterIsAliveInternal())
+			{
+				if (set->exit_on_postmaster_death)
+					proc_exit(1);
+				occurred_events->fd = PGINVALID_SOCKET;
+				occurred_events->events = WL_POSTMASTER_DEATH;
+				occurred_events++;
+				returned_events++;
+			}
+		}
+		else if (cur_event->events & WL_SOCKET_MASK)
+		{
+			WSANETWORKEVENTS resEvents;
+			HANDLE		handle = set->handles[cur_event->pos + 1];
+
+			Assert(cur_event->fd);
+
+			occurred_events->fd = cur_event->fd;
+
+			ZeroMemory(&resEvents, sizeof(resEvents));
+			if (WSAEnumNetworkEvents(cur_event->fd, handle, &resEvents) != 0)
+				elog(ERROR, "failed to enumerate network events: error code %d",
+					 WSAGetLastError());
+			if ((cur_event->events & WL_SOCKET_READABLE) &&
+				(resEvents.lNetworkEvents & FD_READ))
+			{
+				/* data available in socket */
+				occurred_events->events |= WL_SOCKET_READABLE;
+
+				/*------
+				 * WaitForMultipleObjects doesn't guarantee that a read event
+				 * will be returned if the latch 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
+				 * an indefinite hang.
+				 *
+				 * Refer
+				 * https://msdn.microsoft.com/en-us/library/windows/desktop/ms741576(v=vs.85).aspx
+				 * for the behavior of socket events.
+				 *------
+				 */
+				cur_event->reset = true;
+			}
+			if ((cur_event->events & WL_SOCKET_WRITEABLE) &&
+				(resEvents.lNetworkEvents & FD_WRITE))
+			{
+				/* writeable */
+				occurred_events->events |= WL_SOCKET_WRITEABLE;
+			}
+			if ((cur_event->events & WL_SOCKET_CONNECTED) &&
+				(resEvents.lNetworkEvents & FD_CONNECT))
+			{
+				/* connected */
+				occurred_events->events |= WL_SOCKET_CONNECTED;
+			}
+			if ((cur_event->events & WL_SOCKET_ACCEPT) &&
+				(resEvents.lNetworkEvents & FD_ACCEPT))
+			{
+				/* incoming connection could be accepted */
+				occurred_events->events |= WL_SOCKET_ACCEPT;
+			}
+			if (resEvents.lNetworkEvents & FD_CLOSE)
+			{
+				/* EOF/error, so signal all caller-requested socket flags */
+				occurred_events->events |= (cur_event->events & WL_SOCKET_MASK);
+			}
+
+			if (occurred_events->events != 0)
+			{
+				occurred_events++;
+				returned_events++;
+			}
+		}
+
+		/* Is the output buffer full? */
+		if (returned_events == nevents)
+			break;
+
+		/* Have we run out of possible events? */
+		next_pos = cur_event->pos + 1;
+		if (next_pos == set->nevents)
+			break;
+
+		/*
+		 * Poll the rest of the event handles in the array starting at
+		 * next_pos being careful to skip over the initial signal handle too.
+		 * This time we use a zero timeout.
+		 */
+		count = set->nevents - next_pos;
+		rc = WaitForMultipleObjects(count,
+									set->handles + 1 + next_pos,
+									false,
+									0);
+
+		/*
+		 * We don't distinguish between errors and WAIT_TIMEOUT here because
+		 * we already have events to report.
+		 */
+		if (rc < WAIT_OBJECT_0 || rc >= WAIT_OBJECT_0 + count)
+			break;
+
+		/* We have another event to decode. */
+		cur_event = &set->events[next_pos + (rc - WAIT_OBJECT_0)];
+	}
+
+	return returned_events;
+}
+#endif
+
+/*
+ * Return whether the current build options can report WL_SOCKET_CLOSED.
+ */
+bool
+WaitEventSetCanReportClosed(void)
+{
+#if (defined(WAIT_USE_POLL) && defined(POLLRDHUP)) || \
+	defined(WAIT_USE_EPOLL) || \
+	defined(WAIT_USE_KQUEUE)
+	return true;
+#else
+	return false;
+#endif
+}
+
+/*
+ * Get the number of wait events registered in a given WaitEventSet.
+ */
+int
+GetNumRegisteredWaitEvents(WaitEventSet *set)
+{
+	return set->nevents;
+}
+
+#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.
+ */
+static void
+latch_sigurg_handler(SIGNAL_ARGS)
+{
+	if (waiting)
+		sendSelfPipeByte();
+}
+
+/* Send one byte to the self-pipe, to wake up WaitLatch */
+static void
+sendSelfPipeByte(void)
+{
+	int			rc;
+	char		dummy = 0;
+
+retry:
+	rc = write(selfpipe_writefd, &dummy, 1);
+	if (rc < 0)
+	{
+		/* If interrupted by signal, just retry */
+		if (errno == EINTR)
+			goto retry;
+
+		/*
+		 * If the pipe is full, we don't need to retry, the data that's there
+		 * already is enough to wake up WaitLatch.
+		 */
+		if (errno == EAGAIN || errno == EWOULDBLOCK)
+			return;
+
+		/*
+		 * Oops, the write() failed for some other reason. We might be in a
+		 * signal handler, so it's not safe to elog(). We have no choice but
+		 * silently ignore the error.
+		 */
+		return;
+	}
+}
+
+#endif
+
+#if defined(WAIT_USE_SELF_PIPE) || defined(WAIT_USE_SIGNALFD)
+
+/*
+ * Read all available data from self-pipe or signalfd.
+ *
+ * Note: this is only called when waiting = true.  If it fails and doesn't
+ * return, it must reset that flag first (though ideally, this will never
+ * happen).
+ */
+static void
+drain(void)
+{
+	char		buf[1024];
+	int			rc;
+	int			fd;
+
+#ifdef WAIT_USE_SELF_PIPE
+	fd = selfpipe_readfd;
+#else
+	fd = signal_fd;
+#endif
+
+	for (;;)
+	{
+		rc = read(fd, buf, sizeof(buf));
+		if (rc < 0)
+		{
+			if (errno == EAGAIN || errno == EWOULDBLOCK)
+				break;			/* the descriptor is empty */
+			else if (errno == EINTR)
+				continue;		/* retry */
+			else
+			{
+				waiting = false;
+#ifdef WAIT_USE_SELF_PIPE
+				elog(ERROR, "read() on self-pipe failed: %m");
+#else
+				elog(ERROR, "read() on signalfd failed: %m");
+#endif
+			}
+		}
+		else if (rc == 0)
+		{
+			waiting = false;
+#ifdef WAIT_USE_SELF_PIPE
+			elog(ERROR, "unexpected EOF on self-pipe");
+#else
+			elog(ERROR, "unexpected EOF on signalfd");
+#endif
+		}
+		else if (rc < sizeof(buf))
+		{
+			/* we successfully drained the pipe; no need to read() again */
+			break;
+		}
+		/* else buffer wasn't big enough, so read again */
+	}
+}
+
+#endif
+
+static void
+ResOwnerReleaseWaitEventSet(Datum res)
+{
+	WaitEventSet *set = (WaitEventSet *) DatumGetPointer(res);
+
+	Assert(set->owner != NULL);
+	set->owner = NULL;
+	FreeWaitEventSet(set);
+}
+
+/*
+ * Wake up my process if it's currently waiting on a WaitEventSet.
+ *
+ * NB: 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.) XXX
+  *
+ * NB: this function is called from critical sections and signal handlers so
+ * throwing an error is not a good idea.
+ */
+void
+WakeupMyProc(void)
+{
+#if defined(WAIT_USE_SELF_PIPE)
+	if (waiting)
+		sendSelfPipeByte();
+#else
+	if (waiting)
+		kill(MyProcPid, SIGURG);
+#endif
+}
+
+/*
+ * Wake up another process if it's currently waiting.
+ */
+void
+WakeupOtherProc(int pid)
+{
+	kill(pid, SIGURG);
+}
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 0347fc11092..dc3521457c7 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -127,7 +127,7 @@ InitPostmasterChild(void)
 #endif
 
 	/* Initialize process-local latch support */
-	InitializeLatchSupport();
+	InitializeWaitEventSupport();
 	InitProcessLocalLatch();
 	InitializeLatchWaitSet();
 
@@ -188,7 +188,7 @@ InitStandaloneProcess(const char *argv0)
 	InitProcessGlobals();
 
 	/* Initialize process-local latch support */
-	InitializeLatchSupport();
+	InitializeWaitEventSupport();
 	InitProcessLocalLatch();
 	InitializeLatchWaitSet();
 
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
index 66e7a5b7c08..5af32621c0d 100644
--- a/src/include/storage/latch.h
+++ b/src/include/storage/latch.h
@@ -84,10 +84,11 @@
  * use of any generic handler.
  *
  *
- * WaitEventSets 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.
+ * 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-2025, PostgreSQL Global Development Group
@@ -102,6 +103,7 @@
 
 #include <signal.h>
 
+#include "storage/waiteventset.h"
 #include "utils/resowner.h"
 
 /*
@@ -120,53 +122,9 @@ typedef struct Latch
 #endif
 } Latch;
 
-/*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
- */
-#define WL_LATCH_SET		 (1 << 0)
-#define WL_SOCKET_READABLE	 (1 << 1)
-#define WL_SOCKET_WRITEABLE  (1 << 2)
-#define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
-#define WL_POSTMASTER_DEATH  (1 << 4)
-#define WL_EXIT_ON_PM_DEATH	 (1 << 5)
-#ifdef WIN32
-#define WL_SOCKET_CONNECTED  (1 << 6)
-#else
-/* avoid having to deal with case on platforms not requiring it */
-#define WL_SOCKET_CONNECTED  WL_SOCKET_WRITEABLE
-#endif
-#define WL_SOCKET_CLOSED 	 (1 << 7)
-#ifdef WIN32
-#define WL_SOCKET_ACCEPT	 (1 << 8)
-#else
-/* avoid having to deal with case on platforms not requiring it */
-#define WL_SOCKET_ACCEPT	WL_SOCKET_READABLE
-#endif
-#define WL_SOCKET_MASK		(WL_SOCKET_READABLE | \
-							 WL_SOCKET_WRITEABLE | \
-							 WL_SOCKET_CONNECTED | \
-							 WL_SOCKET_ACCEPT | \
-							 WL_SOCKET_CLOSED)
-
-typedef struct WaitEvent
-{
-	int			pos;			/* position in the event data structure */
-	uint32		events;			/* triggered events */
-	pgsocket	fd;				/* socket fd associated with event */
-	void	   *user_data;		/* pointer provided in AddWaitEventToSet */
-#ifdef WIN32
-	bool		reset;			/* Is reset of the event required? */
-#endif
-} WaitEvent;
-
-/* forward declaration to avoid exposing latch.c implementation details */
-typedef struct WaitEventSet WaitEventSet;
-
 /*
  * prototypes for functions in latch.c
  */
-extern void InitializeLatchSupport(void);
 extern void InitLatch(Latch *latch);
 extern void InitSharedLatch(Latch *latch);
 extern void OwnLatch(Latch *latch);
@@ -174,22 +132,10 @@ extern void DisownLatch(Latch *latch);
 extern void SetLatch(Latch *latch);
 extern void ResetLatch(Latch *latch);
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
-extern void FreeWaitEventSet(WaitEventSet *set);
-extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
-extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  Latch *latch, void *user_data);
-extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch);
-
-extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
-							 WaitEvent *occurred_events, int nevents,
-							 uint32 wait_event_info);
 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);
-extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
-extern bool WaitEventSetCanReportClosed(void);
 
 #endif							/* LATCH_H */
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
new file mode 100644
index 00000000000..022960fe888
--- /dev/null
+++ b/src/include/storage/waiteventset.h
@@ -0,0 +1,95 @@
+/*-------------------------------------------------------------------------
+ *
+ * waiteventset.h
+ *		ppoll() / pselect() like interface for waiting for events
+ *
+ * WaitEventSets 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 WaitInterrupt or WaitInterruptOrSocket.
+ *
+ * WaitEventSetWait 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.
+ *
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/storage/waiteventset.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef WAITEVENTSET_H
+#define WAITEVENTSET_H
+
+#include "utils/resowner.h"
+
+/*
+ * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
+ * WaitEventSetWait().
+ */
+#define WL_LATCH_SET		 (1 << 0)
+#define WL_SOCKET_READABLE	 (1 << 1)
+#define WL_SOCKET_WRITEABLE  (1 << 2)
+#define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
+#define WL_POSTMASTER_DEATH  (1 << 4)
+#define WL_EXIT_ON_PM_DEATH	 (1 << 5)
+#ifdef WIN32
+#define WL_SOCKET_CONNECTED  (1 << 6)
+#else
+/* avoid having to deal with case on platforms not requiring it */
+#define WL_SOCKET_CONNECTED  WL_SOCKET_WRITEABLE
+#endif
+#define WL_SOCKET_CLOSED 	 (1 << 7)
+#ifdef WIN32
+#define WL_SOCKET_ACCEPT	 (1 << 8)
+#else
+/* avoid having to deal with case on platforms not requiring it */
+#define WL_SOCKET_ACCEPT	WL_SOCKET_READABLE
+#endif
+#define WL_SOCKET_MASK		(WL_SOCKET_READABLE | \
+							 WL_SOCKET_WRITEABLE | \
+							 WL_SOCKET_CONNECTED | \
+							 WL_SOCKET_ACCEPT | \
+							 WL_SOCKET_CLOSED)
+
+typedef struct WaitEvent
+{
+	int			pos;			/* position in the event data structure */
+	uint32		events;			/* triggered events */
+	pgsocket	fd;				/* socket fd associated with event */
+	void	   *user_data;		/* pointer provided in AddWaitEventToSet */
+#ifdef WIN32
+	bool		reset;			/* Is reset of the event required? */
+#endif
+} WaitEvent;
+
+/* forward declarations to avoid exposing waiteventset.c implementation details */
+typedef struct WaitEventSet WaitEventSet;
+
+typedef struct Latch Latch;
+
+/*
+ * prototypes for functions in waiteventset.c
+ */
+extern void InitializeWaitEventSupport(void);
+
+extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern void FreeWaitEventSet(WaitEventSet *set);
+extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
+extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
+							  Latch *latch, void *user_data);
+extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch);
+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);
+
+extern void WakeupMyProc(void);
+extern void WakeupOtherProc(int pid);
+
+#endif							/* WAITEVENTSET_H */
-- 
2.39.5



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-02-28 20:24     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-03-05 19:25       ` Andres Freund <[email protected]>
  2025-03-05 23:28         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Andres Freund @ 2025-03-05 19:25 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

On 2025-02-28 22:24:56 +0200, Heikki Linnakangas wrote:
> I noticed that the ShutdownLatchSupport() function is unused. The first
> patch removes it.

Looks like that's the case since

commit 80a8f95b3bca6a80672d1766c928cda34e979112
Author: Andres Freund <[email protected]>
Date:   2021-08-13 05:49:26 -0700
 
    Remove support for background workers without BGWORKER_SHMEM_ACCESS.

Oops.

LGTM.


> The second patch makes it possible to use ModifyWaitEvent() to switch
> between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH. WaitLatch() used to
> modify WaitEventSet->exit_on_postmaster_death directly, now it uses
> ModifyWaitEvent() for that. That's needed because with the final patch,
> WaitLatch() is in a different source file than WaitEventSet, so it cannot
> directly modify its field anymore.


> @@ -505,8 +513,14 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
>  	if (!(wakeEvents & WL_LATCH_SET))
>  		latch = NULL;
>  	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
> -	LatchWaitSet->exit_on_postmaster_death =
> -		((wakeEvents & WL_EXIT_ON_PM_DEATH) != 0);
> +
> +	/*
> +	 * Update the event set for whether WL_EXIT_ON_PM_DEATH or
> +	 * WL_POSTMASTER_DEATH was requested.  This is also cheap.
> +	 */

"for whether" sounds odd to me.

I'd remove the "also" in the second sentence.


> @@ -1037,15 +1051,19 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
>  		(!(event->events & WL_LATCH_SET) || set->latch == latch))
>  		return;
>  
> -	if (event->events & WL_LATCH_SET &&
> -		events != event->events)
> +	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
> +	if (event->events & WL_POSTMASTER_DEATH)

Hm, this only supports switching from WL_POSTMASTER_DEATH to
WL_EXIT_ON_PM_DEATH, not the other way round, right?


>  	{
> -		elog(ERROR, "cannot modify latch event");
> +		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
> +			elog(ERROR, "cannot modify postmaster death event");
> +		set->exit_on_postmaster_death = ((events & WL_EXIT_ON_PM_DEATH) != 0);
> +		return;
>  	}


> -	if (event->events & WL_POSTMASTER_DEATH)
> +	if (event->events & WL_LATCH_SET &&
> +		events != event->events)
>  	{
> -		elog(ERROR, "cannot modify postmaster death event");
> +		elog(ERROR, "cannot modify latch event");
>  	}

Why did you reorder this? I don't have a problem with it, I just can't quite
follow.


> The third patch is mechanical and moves existing code. The file header
> comments in the modified files are perhaps worth reviewing. They are also
> just existing text moved around, but there was some small decisions on what
> exactly should go where.
> 
> I'll continue working on the other parts, but these patches seems ready for
> commit already.

Interestingly git diff's was pretty annoying to read, even with --color-moved,
unless I used -M100 (setting the rename detection to require 100%
renamed). With "-M100 --color-moved=dimmed-zebra" it looks a lot saner.


> From 7bd0d2f98a1b9d7ad40f3f72cd1c93430c1d7cee Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Fri, 28 Feb 2025 16:42:15 +0200
> Subject: [PATCH 3/3] Split WaitEventSet functions to separate source file
> 
> latch.c now only contains the Latch related functions, which build on
> the WaitEventSet abstraction. Most of the platform-dependent stuff is
> now in waiteventset.c.


>  include $(top_srcdir)/src/backend/common.mk
> diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
> index ab601c748f8..aae0cf7577d 100644
> --- a/src/backend/storage/ipc/latch.c
> +++ b/src/backend/storage/ipc/latch.c

>  #include "miscadmin.h"

It's a bit weird that MyLatch is declared in miscadmin.h, but that's not this
patch's fault.



> diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
> new file mode 100644
> index 00000000000..35e836d3398

> +#include "storage/proc.h"

proc.h wasn't previously included, and it's not clear to me why it'd be needed
now? If I remove it, things still compile, and I verified it's not indirectly
included.

Perhaps you had WakeupMyProc() in proc.c earlier?


> +/*
> + * 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.
> + *
> + * 'pos' is the id returned by AddWaitEventToSet.
> + */
> +void
> +ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
> +{
> ...
> +
> +	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
> +	if (event->events & WL_POSTMASTER_DEATH)
> +	{
> +		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
> +			elog(ERROR, "cannot modify postmaster death event");
> +		set->exit_on_postmaster_death =	((events & WL_EXIT_ON_PM_DEATH) != 0);

FWIW, --color-moved flagged this line as having changed. That confused me for
a bit, but it seems that somehow you ended up with a tab after the =. pgindent
wants to remove it too.


> +int
> +WaitEventSetWait(WaitEventSet *set, long timeout,
> +				 WaitEvent *occurred_events, int nevents,
> +				 uint32 wait_event_info)
> +{
> ...
> +		if (set->latch && !set->latch->is_set)
> +		{
> +			/* about to sleep on a latch */
> +			set->latch->maybe_sleeping = true;
> +			pg_memory_barrier();
> +			/* and recheck */
> +		}

It's a bit ugly that we modify the latch state here - but I don't think the
alternatives are really better...


> +/*
> + * Wake up my process if it's currently waiting on a WaitEventSet.

Hm - that's overstating it a bit, I think. If the WES doesn't wait on the
latch set, this won't wake the process, right?


> +/*
> + * Wake up another process if it's currently waiting.
> + */
> +void
> +WakeupOtherProc(int pid)
> +{
> +	kill(pid, SIGURG);
> +}

Dito, I think?


> diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
> index 66e7a5b7c08..5af32621c0d 100644
> --- a/src/include/storage/latch.h
> +++ b/src/include/storage/latch.h
> @@ -84,10 +84,11 @@
>   * use of any generic handler.
>   *
>   *
> - * WaitEventSets 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.
> + * 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-2025, PostgreSQL Global Development Group
> @@ -102,6 +103,7 @@
>  
>  #include <signal.h>
>  
> +#include "storage/waiteventset.h"

Perhaps worth commenting that this is included here for backward compat?


>  #include "utils/resowner.h"

I don't think the resowner.h include is still needed in latch.h


> @@ -0,0 +1,95 @@
> +/*-------------------------------------------------------------------------
> + *
> + * waiteventset.h
> + *		ppoll() / pselect() like interface for waiting for events
> + *
> + * WaitEventSets 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 WaitInterrupt or WaitInterruptOrSocket.

I don't think WaitInterrupt/WaitInterruptOrSocket exist at this stage?


These minor things aside, this looks good to me.

Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-02-28 20:24     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-03-05 19:25       ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2025-03-05 23:28         ` Heikki Linnakangas <[email protected]>
  0 siblings, 0 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2025-03-05 23:28 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 05/03/2025 21:25, Andres Freund wrote:
> On 2025-02-28 22:24:56 +0200, Heikki Linnakangas wrote:
>> The second patch makes it possible to use ModifyWaitEvent() to switch
>> between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH. WaitLatch() used to
>> modify WaitEventSet->exit_on_postmaster_death directly, now it uses
>> ModifyWaitEvent() for that. That's needed because with the final patch,
>> WaitLatch() is in a different source file than WaitEventSet, so it cannot
>> directly modify its field anymore.
> 
> 
>> @@ -505,8 +513,14 @@ WaitLatch(Latch *latch, int wakeEvents, long timeout,
>>   	if (!(wakeEvents & WL_LATCH_SET))
>>   		latch = NULL;
>>   	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
>> -	LatchWaitSet->exit_on_postmaster_death =
>> -		((wakeEvents & WL_EXIT_ON_PM_DEATH) != 0);
>> +
>> +	/*
>> +	 * Update the event set for whether WL_EXIT_ON_PM_DEATH or
>> +	 * WL_POSTMASTER_DEATH was requested.  This is also cheap.
>> +	 */
> 
> "for whether" sounds odd to me.
> 
> I'd remove the "also" in the second sentence.

I removed the whole comment. The "also" was referring to the previous 
comment in the function just above what's shown above, but now that I 
look more closely, the other comment already covered the postmaster 
death event.

>> @@ -1037,15 +1051,19 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
>>   		(!(event->events & WL_LATCH_SET) || set->latch == latch))
>>   		return;
>>   
>> -	if (event->events & WL_LATCH_SET &&
>> -		events != event->events)
>> +	/* Allow switching between WL_POSTMASTER_DEATH and WL_EXIT_ON_PM_DEATH */
>> +	if (event->events & WL_POSTMASTER_DEATH)
> 
> Hm, this only supports switching from WL_POSTMASTER_DEATH to
> WL_EXIT_ON_PM_DEATH, not the other way round, right?

Fixed.

Checking "if (event->events & WL_POSTMASTER_DEATH)" was in fact correct, 
because in a WaitEventSet, WL_EXIT_ON_PM_DEATH is converted to 
WL_POSTMASTER_DEATH with exit_on_postmaster_death = true. But it was 
still broken because when switching from WL_EXIT_ON_PM_DEATH to 
WL_POSTMASTER_DEATH, we took the "events == event->events" quick-exit 
path before reaching this, for the same reason. I fixed that by moving 
this check before the "events == event->events" check.

>>   	{
>> -		elog(ERROR, "cannot modify latch event");
>> +		if (events != WL_POSTMASTER_DEATH && events != WL_EXIT_ON_PM_DEATH)
>> +			elog(ERROR, "cannot modify postmaster death event");
>> +		set->exit_on_postmaster_death = ((events & WL_EXIT_ON_PM_DEATH) != 0);
>> +		return;
>>   	}
> 
> 
>> -	if (event->events & WL_POSTMASTER_DEATH)
>> +	if (event->events & WL_LATCH_SET &&
>> +		events != event->events)
>>   	{
>> -		elog(ERROR, "cannot modify postmaster death event");
>> +		elog(ERROR, "cannot modify latch event");
>>   	}
> 
> Why did you reorder this? I don't have a problem with it, I just can't quite
> follow.

I wanted to keep the non-error returns together for aesthetic reasons, 
that's all. But that point is moot now, because as I mentioned above, I 
had to move the check for switching between WL_POSTMASTER_DEATH and 
WL_EXIT_ON_PM_DEATH anyway.

>> The third patch is mechanical and moves existing code. The file header
>> comments in the modified files are perhaps worth reviewing. They are also
>> just existing text moved around, but there was some small decisions on what
>> exactly should go where.
>>
>> I'll continue working on the other parts, but these patches seems ready for
>> commit already.
> 
> Interestingly git diff's was pretty annoying to read, even with --color-moved,
> unless I used -M100 (setting the rename detection to require 100%
> renamed). With "-M100 --color-moved=dimmed-zebra" it looks a lot saner.

Thanks for the tip! I didn't know about --color-moved.

>> diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
>> new file mode 100644
>> index 00000000000..35e836d3398
> 
>> +#include "storage/proc.h"
> 
> proc.h wasn't previously included, and it's not clear to me why it'd be needed
> now? If I remove it, things still compile, and I verified it's not indirectly
> included.
> 
> Perhaps you had WakeupMyProc() in proc.c earlier?

Thanks, removed.

>> +/*
>> + * Wake up my process if it's currently waiting on a WaitEventSet.
> 
> Hm - that's overstating it a bit, I think. If the WES doesn't wait on the
> latch set, this won't wake the process, right?

It does wake up the process from the sleeping syscall in 
WaitEventSetWaitBlock(). WaitEventSetWait() will re-check the wait 
conditions and go back to sleep, if it's not waiting for the latch set.

I think the problem here is that "waiting on a WaitEventSet" is 
ambiguous. I'll change it to "... if it's currently sleeping in 
WaitEventSetWaitBlock()".

>> diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
>> index 66e7a5b7c08..5af32621c0d 100644
>> --- a/src/include/storage/latch.h
>> +++ b/src/include/storage/latch.h
>> @@ -84,10 +84,11 @@
>>    * use of any generic handler.
>>    *
>>    *
>> - * WaitEventSets 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.
>> + * 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-2025, PostgreSQL Global Development Group
>> @@ -102,6 +103,7 @@
>>   
>>   #include <signal.h>
>>   
>> +#include "storage/waiteventset.h"
> 
> Perhaps worth commenting that this is included here for backward compat?

Hmm. You're right that it's not strictly required here. In practice 
though, all callers of WaitLatch() will need to include it, because the 
WL_* argument flags are defined there. I'm not sure if we have a policy 
on that kind of convenience #includes. I'll add a comment.

>> @@ -0,0 +1,95 @@
>> +/*-------------------------------------------------------------------------
>> + *
>> + * waiteventset.h
>> + *		ppoll() / pselect() like interface for waiting for events
>> + *
>> + * WaitEventSets 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 WaitInterrupt or WaitInterruptOrSocket.
> 
> I don't think WaitInterrupt/WaitInterruptOrSocket exist at this stage?

Fixed.

> These minor things aside, this looks good to me.

Thanks! Committed with those fixes and some minor WIN32 build fixes.

-- 
Heikki Linnakangas
Neon (https://neon.tech)







^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2025-03-06 00:47     ` Heikki Linnakangas <[email protected]>
  2025-03-06 16:43       ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2 siblings, 2 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2025-03-06 00:47 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Here's a new patch set. It includes the previous work, and also goes the 
whole hog and replaces procsignals and many other signalling with 
interrupts. It's based on Thomas's 
v3-0002-Redesign-interrupts-remove-ProcSignals.patch much earlier in 
this thread.

That's included as a separate patch in the patch set, on top of the 
previous ones. I like the end state, but I'm not sure if that's the most 
sensible way to commit these. It might make more sense to commit the 
procsignal->interrupt changes first, and the commit to remove latches 
second. Or just commit all in one giant commit. Not sure.

In any case, I hope this split is easier to review at least than just 
squashing them all tgoether...

I haven't addressed all your comments yet, and there are some TODO/FIXME 
comments. More details below.

On 28/01/2025 19:01, Andres Freund wrote:
> On 2024-12-02 16:39:28 +0200, Heikki Linnakangas wrote:
>>  From eff8de11fbfea4e2aadce9c1d71452b0f5a1b80b Mon Sep 17 00:00:00 2001
>> From: Heikki Linnakangas <[email protected]>
>> Date: Mon, 2 Dec 2024 15:51:54 +0200
>> Subject: [PATCH v5 1/4] Replace Latches with Interrupts
> 
> Your email has only three attachements - I assume the fourth is something
> local that didn't need to be shared?

Yeah, it was a work-in-progress patch to convert config-reload requests 
to the interrupts, to see how it works.

>> The Latch was a flag, with an inter-process signalling mechanism that
>> allowed a process to wait for the Latch to be set. My original vision
>> for Latches was that you could have different latches for different
>> things that you need to wait for, but in practice, almost all code
>> used one "process latch" that was shared for all wakeups. The only
>> exception was the "recoveryWakeupLatch". I think the reason that we
>> ended up just sharing the same latch for all wakeups is that it was
>> cumbersome in practice to deal with multiple latches. For starters,
>> there was no machinery for waiting for multiple latches at the same
>> time. Secondly, changing the "ownership" of a latch needed extra
>> locking or other coordination. Thirdly, each inter-process latch
>> needed to be initialized at postmaster startup time.
>>
>> This patch embraces the reality of how Latches were used, and replaces
>> the Latches with a per-process interrupt mask. You can no longer
>> allocate Latches in arbitrary shared memory structs and an interrupt
>> is always directed at a particular process, addressed by its
>> ProcNumber.  Each process has a bitmask of pending interrupts in
>> PGPROC.
> 
> That doesn't really remove the need for inter-process coordination to know
> what ProcNumber corresponds to what...

Right. Was there a comment that claims it does or something, or why do 
you point that out?

>> This commit introduces two interrupt bits. INTERRUPT_GENERAL replaces
>> the general-purpose per-process latch. All code that previously set a
>> process's process latch now sets its INTERRUPT_GENERAL interrupt bit
>> instead.
> 
> Half-formed-at-best thought: I wonder if we should split the "interrupt
> yourself in response to a signal" cases from "another process wakes you up",
> even in the initial commit. They seem rather different to me.

Hmm. Some interrupts are indeed clearly only sent within the process, 
while others are sent across processes. Not sure where that distinction 
would help though. Just group them together and have comments? Or what 
did you have in mind?

There are other ways to group the interrupts. Some are harmless if 
they're sent to a wrong process. Others are not. Some are handled by 
CHECK_FOR_INTERRUTPS, others are not.

>> diff --git a/src/include/storage/interrupt.h b/src/include/storage/interrupt.h
>> new file mode 100644
>> index 0000000000..1da05369c3
>> --- /dev/null
>> +++ b/src/include/storage/interrupt.h
> 
> So we now have postmaster/interrupt.[ch] and storage/interrupt.h /
> storage/ipc/interrupt.c.

In the attached version, I renamed the existing 
postmaster/interrupt.[ch] to postmaster/interrupt_handlers.[ch]. I think 
that helps.

Now that I look at the big picture again, though, I'm thinking that they 
should perhaps be merged, so that there would be only 
postmaster/interrupt.[ch]. That's how Thomas had them in the original 
procsignal->interrupt patches. There's not much left of the old 
postmaster/interrupt.[ch], and it's all related to interrupts anyway.

>> + * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
>> + * consists of tasks that are safe to perform at most times.  They can be
>> + * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
>> + *
>> + *
>> + * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
>> + *
>> + * for (;;)
>> + * {
>> + *	   ClearInterrupt(INTERRUPT_GENERAL);
>> + *	   if (work to do)
>> + *		   Do Stuff();
>> + *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
>> + * }
> 
> I don't particularly like that there's now dozens of places that need to do
> bit-shifting.
> 
> One reason I don't like it is that, for example, this should actually be 1U <<
> INTERRUPT_GENERAL, and at some later point we might need it to be a 64bit
> integer due to a larger number of interrupts, which will require going to all
> extensions and updating them.
> 
> Perhaps WaitInterrupt should accept just a single interrupt type and
> WaitEventSets should allow registering/reporting different interrupts to wait
> for as different events?
> 
> Or perhaps we could introduce an interrupt mask type?

I changed the values of INTERRUPT_* to be bitmasks, so that you can do 
e.g. "INTERRUPT_GENERAL | INTERRUPT_CONFIG_RELOAD" without any 
bit-shifting. All the functions like ClearInterrupt, RaiseInterrupt, 
SendInterrupt now work with a bitmask, it doesn't have to be just a 
single flag anymore.

>> +extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
>> +
>> +/*
>> + * Flags in the pending interrupts bitmask. Each value represents one bit in
>> + * the bitmask.
>> + */
>> +typedef enum
> 
> Personally I prefer if we name the objects underlying typedefs, as otherwise
> some tools will label them "anonymous". And, while it doesn't matter for
> enums, which we can't forward declare in our version of C, it also is required
> to make forward declarations work.

Ok

>> +/*
>> + * Test an interrupt flag.
>> + */
>> +static inline bool
>> +InterruptIsPending(InterruptType reason)
>> +{
>> +	return (pg_atomic_read_u32(MyPendingInterrupts) & (1 << reason)) != 0;
>> +}
> 
> This has no memory ordering guarantees implied - is that correct?  I think
> that could lead to missing interrupts in some cases.

I added pg_read_barrier() and pg_write_barrier() calls to these. I'm not 
100% if that's really needed, or if I got them right, though.

> Given functions like ClearInterrupt() are <Verb><Object>, why differ here?
> 
> 
>> +/*
>> + * Test an interrupt flag.
>> + */
>> +static inline bool
>> +InterruptsPending(uint32 mask)
>> +{
>> +	return (pg_atomic_read_u32(MyPendingInterrupts) & (mask)) != 0;
>> +}
> 
> It seems somewhat confusing to have two naming patterns for this and the
> closely related InterruptIsPending().
> 
> Maybe IsInterruptPending() and AreInterruptsPending()? Or
> IsInterruptPendingMask()?
> 
> I think I like IsInterruptPendingMask() better, because we're not actually
> waiting for multiple interrupts, we're waiting for one out of multiple
> possible interrupts to arrive

There's now just a single function, InterruptPending(mask), which works 
with a single bit or mask of several bits.

I agree it's not a great name, I just didn't get around to think about 
that yet.

>> @@ -4476,7 +4458,10 @@ CheckPromoteSignal(void)
>>   void
>>   WakeupRecovery(void)
>>   {
>> -	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
>> +	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
>> +
>> +	if (procno != INVALID_PROC_NUMBER)
>> +		SendInterrupt(INTERRUPT_RECOVERY_CONTINUE, procno);
>>   }
> 
> Not a new problem, and not really a problem for the startup process, that we
> don't expect to die, but: Code like this could obviously lead to interrupts
> being sent to the "former" owner of a procno.
> 
> I think either the file header of SendInterrupt() should mention that risk and
> that code has to be aware of it.

Yeah, that's fair. As the patch stands, the rules are different for 
different interrupts. Some interrupts are OK to send to random backends; 
no harm done. But others, like INTERRUPT_DIE or INTERRUPT_QUERY_CANCEL, 
are not. I think we need a mechanism to reliably send an interrupt to 
the right backend, without race conditions if the backend exits 
concurrently. Holding ProcArrayLock works, but that's a pretty big hammer.

> Should we explicitly define the interrupt state a process has at startup?

Makes sense. (Not done yet, it's a TODO)

>> +/*
>> + * Set an interrupt flag in this backend.
>> + */
>> +void
>> +RaiseInterrupt(InterruptType reason)
> 
>> +{
>> +	uint32		old_pending;
>> +
>> +	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << reason);
>> +
>> +	/*
>> +	 * If the process is currently blocked waiting for an interrupt to arrive,
>> +	 * and the interrupt wasn't already pending, wake it up.
>> +	 */
>> +	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
> 
> This is a somewhat hard to read line. Maybe it's worth breaking it out into
> two conditions? Or use a local variable?

It's a little better now that the bitshifts are gone, but still a fair 
point..

> I was chatting with Heikki about this patch and he mentioned that he recalls a
> patch that did some work to unify the signal replacement, procsignal.h and
> CHECK_FOR_INTERRUPTS(). Thomas, that was probably from you? Do you have a
> pointer, if so?
> 
> It does seem like we're going to have to do some unification here. We have too
> many different partially overlapping, partially collaborating systems here.
> 
> 
> - procsignals - kinda like the interrupts here, but not quite. Probably can't
>   just merge them 1:1 into the proposed mechanism, or we'll run out of bits
>   rather soon.  I don't know if we want the relevant multiplexing to be built
>   into interrupt.h or not.
> 
>   Or we ought to redesign this mechanism to deal with more than 32 types of
>   interrupt.

In this patch, 29 out of 32 bits are used. Yeah, that doesn't leave much 
headroom.

Some ideas for addressing that:

1. Use 64-bit atomics. Are there any architectures left where that would 
not be acceptable?

2. Use 64-bit integers, but for the actual signaling part, split it into 
two 32-bit atomic words. Waiting on the interrupts would need to check 
both, but that seems fine from a performance point of view.

3. If we need > 64 bits, things get a bit awkward as you can no longer 
have a single integer to represent a bitmask that includes all the bits. 
That makes the function signatures more ugly, you need to have two 
arguments like interruptMask1 and interruptMask2 or somehting. But 
should basically work otherwise.

4. Multiplex the interrupts so that we need fewer of them. I think all 
the recovery conflict interrupts could be merged into one, for example, 
if we had a separate bitmask somewhere else in PGPROC to indicate which 
ones are set. The limitation would be that if interrupts are multiplexed 
together into one bit, you could only wait for all or none of them.


I'd like to not have to treat these bits as a scarce resource. It'd be 
nice to use even more distinct interrupts than what's in the patch now 
for different things, just for clarity. And I'd like to make it possible 
for extensions to allocate interrupt bits too. (Not included in these 
patches yet)

I'm leaning towards 1. or 2. at the moment. 64 bits should be enough for 
a long time.

> - pmsignal.h - basically the same thing as here, except for signals sent *too*
>   postmaster.
> 
>   It might or might not be required to keep this separate. There are different
>   "reliability" requirements...

I think pmsignal.c could be rewritten to use interrupts fairly easily. I 
didn't do that yet though. For that, postmaster needs to have a PGPROC 
slot, or at least a special reserved ProcNumber, but it doesn't seem hard.

> - CHECK_FOR_INTERRUPTS(), which uses the mechanism introduced here to react to
>   signals while blocked, using RaiseInterrupt() (whereas it did SetLatch()
>   before).
> 
>   A fairly simple improvement would be to use different InterruptType for
>   e.g. termination and query cancellation.

That's done now.

>   But even with this infrastructure in place, we can't *quite* get away from
>   signal handlers, because we need some way to set at the very least
>   InterruptPending (and perhaps ProcDiePending, QueryCancelPending etc,
>   although those could be replaced with InterruptIsPending()). The
>   infrastructure introduced with these patches provides neither a way to set
>   those variables in response to delivery of an interrupt, nor can we
>   currently set them from another backend, as they are global variables.
> 
>   We could of course do InterruptsPending(~0) in in
>   INTERRUPTS_PENDING_CONDITION(), but it would require analyzing the increased
>   indirection overhead as well as the "timeliness" guarantees.
> 
>   Today we don't need a memory barrier around checking InterruptPending,
>   because delivery of a signal delivery (via SetLatch()) ensures that. But I
>   think we would need one if we were to not send signals for
>   cancellation/terminations etc. I.e. right now the overhead of delivery is
>   bigger, but checking if there pending signals is cheaper.

That's changed heavily in this latest patch. All the *Pending global 
variables are gone, they are replaced with InterruptPending() calls.


>   Another aspect of this is that we're cramming more and more code into
>   ProcessInterrupts(), in a long weave of ifs.  I wonder if somewhere along
>   ipc/interrupt.h there should be a facility to register callbacks to react to
>   delivered interrupts.  It'd need to be a bit more than just a mapping of
>   InterruptType to callback, because of things like InterruptHoldoffCount,
>   CritSectionCount, QueryCancelHoldoffCount.

+1 for the general idea, but I didn't pursue it yet.

There are some changes to CHECK_FOR_INTERRUPTS() and the holdoff counts 
here already though. The pattern for waiting for something now looks 
like this:


for (;;)
{
     CHECK_FOR_INTERRUPTS();

     /*
      * The baker will wake us up with INTERRUPT_GENERAL when the cake
      * is ready.
      */
     ClearInterrupt(INTERRUPT_GENERAL);
     if (IsTheCakeBakedYet())
         break;

     WaitInterrupt(CheckForInterruptsMask | INTERRUPT_GENERAL, ...);
}

CheckForInterruptsMask includes INTERRUPT_BARRIER, INTERRUPT_DIE, 
INTERRUPT_QUERY_CANCEL, and all the other interrupts that are handled by 
CHECK_FOR_INTERRUPTS(). However, when you call HOLD_INTERRUPTS(), 
INTERRUPT_DIE is removed from CheckForInterruptsMask.

(I'm not wedded to the name CheckForInterruptsMask. Thomas called them 
"regular interrupts" in his patch. I think I used the term "standard 
interrupts" somewhere.)

> Other questions:
> 
> - Can ipc/interrupts.c style interrupts be sent by postmaster? I think they
>   ought to before long.

Yes. The postmaster does that now to notify backends when background 
workers are launched or terminated.

-- 
Heikki Linnakangas
Neon (https://neon.tech)


Attachments:

  [text/x-patch] v6-0001-Rename-postmaster-interrupt.c-to-interrupt_handle.patch (14.1K, ../../[email protected]/2-v6-0001-Rename-postmaster-interrupt.c-to-interrupt_handle.patch)
  download | inline diff:
From 71fc3127621f6da6131236423d2ce44385cfbe70 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 1 Mar 2025 00:18:03 +0200
Subject: [PATCH v6 1/5] Rename postmaster/interrupt.c to interrupt_handlers.c

---
 contrib/pg_prewarm/autoprewarm.c                       |  2 +-
 src/backend/commands/vacuum.c                          |  2 +-
 src/backend/postmaster/Makefile                        |  2 +-
 src/backend/postmaster/autovacuum.c                    |  2 +-
 src/backend/postmaster/bgwriter.c                      |  2 +-
 src/backend/postmaster/checkpointer.c                  |  2 +-
 .../postmaster/{interrupt.c => interrupt_handlers.c}   |  8 ++++----
 src/backend/postmaster/meson.build                     |  2 +-
 src/backend/postmaster/pgarch.c                        |  2 +-
 src/backend/postmaster/syslogger.c                     |  2 +-
 src/backend/postmaster/walsummarizer.c                 |  2 +-
 src/backend/postmaster/walwriter.c                     |  2 +-
 src/backend/replication/logical/applyparallelworker.c  |  2 +-
 src/backend/replication/logical/launcher.c             |  2 +-
 src/backend/replication/logical/slotsync.c             |  2 +-
 src/backend/replication/logical/worker.c               |  2 +-
 src/backend/replication/slot.c                         |  2 +-
 src/backend/replication/walreceiver.c                  |  2 +-
 src/backend/replication/walsender.c                    |  2 +-
 src/backend/tcop/postgres.c                            |  2 +-
 src/backend/utils/init/miscinit.c                      |  2 +-
 .../postmaster/{interrupt.h => interrupt_handlers.h}   | 10 +++++-----
 src/test/modules/worker_spi/worker_spi.c               |  2 +-
 23 files changed, 30 insertions(+), 30 deletions(-)
 rename src/backend/postmaster/{interrupt.c => interrupt_handlers.c} (94%)
 rename src/include/postmaster/{interrupt.h => interrupt_handlers.h} (82%)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index b45755b3347..1d7facf1f36 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -32,7 +32,7 @@
 #include "access/xact.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index e81c9a8aba3..e25ab97bc71 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -46,7 +46,7 @@
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..ee43e446c65 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,7 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
+	interrupt_handlers.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 800815dfbcc..cdbdc0f3b80 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -86,7 +86,7 @@
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index a688cc5d2a1..4d2165fa998 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -37,7 +37,7 @@
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 0e228d143a0..e3759a7a80b 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,7 +47,7 @@
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/syncrep.h"
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt_handlers.c
similarity index 94%
rename from src/backend/postmaster/interrupt.c
rename to src/backend/postmaster/interrupt_handlers.c
index 0ae9bf906ec..14fd74b46d3 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt_handlers.c
@@ -1,13 +1,13 @@
 /*-------------------------------------------------------------------------
  *
- * interrupt.c
- *	  Interrupt handling routines.
+ * interrupt_handlers.c
+ *	  Common signal and interrupt handling routines.
  *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
+ *	  src/backend/postmaster/interrupt_handlers.c
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +17,7 @@
 #include <unistd.h>
 
 #include "miscadmin.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/procsignal.h"
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 0008603cfee..94e8a8a9e3f 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,7 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
+  'interrupt_handlers.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index dbe4e1d426b..1cd2ae87e18 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -37,7 +37,7 @@
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 50c2edec1f6..2a61a8b766b 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -39,7 +39,7 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index ccba0f84e6e..3184961462a 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -35,7 +35,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 0380601bcbb..de16ac4a956 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -49,7 +49,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/walwriter.h"
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index d25085d3515..4d2d3fd2c56 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -160,7 +160,7 @@
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index a3c7adbf1a8..434bb1f6f62 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -29,7 +29,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 2c0a7439be4..44cc5c6fc37 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -55,7 +55,7 @@
 #include "commands/dbcommands.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 31ab69ea13a..67d0647aa32 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -165,7 +165,7 @@
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 719e531eb90..ca271330594 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -46,7 +46,7 @@
 #include "common/string.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
 #include "replication/walsender_private.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 83129cb92af..7392e89767d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -64,7 +64,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 446d10c1a7d..ecd96ad7dbc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -68,7 +68,7 @@
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 947ffb40421..60ab8cd40c7 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -53,7 +53,7 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index dc3521457c7..34b8450b53c 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -38,7 +38,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
diff --git a/src/include/postmaster/interrupt.h b/src/include/postmaster/interrupt_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/postmaster/interrupt_handlers.h
index 3bf49320846..b3a8c38353e 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/postmaster/interrupt_handlers.h
@@ -1,7 +1,7 @@
 /*-------------------------------------------------------------------------
  *
- * interrupt.h
- *	  Interrupt handling routines.
+ * interrupt_handlers.h
+ *	  Common signal and interrupt handling routines.
  *
  * Responses to interrupts are fairly varied and many types of backends
  * have their own implementations, but we provide a few generic things
@@ -11,13 +11,13 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/include/postmaster/interrupt.h
+ *	  src/include/postmaster/interrupt_handlers.h
  *
  *-------------------------------------------------------------------------
  */
 
-#ifndef INTERRUPT_H
-#define INTERRUPT_H
+#ifndef INTERRUPT_HANDLERS_H
+#define INTERRUPT_HANDLERS_H
 
 #include <signal.h>
 
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index 5b87d4f7038..1e4e813314f 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -25,7 +25,7 @@
 /* These are always necessary for a bgworker */
 #include "miscadmin.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
+#include "postmaster/interrupt_handlers.h"
 #include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
-- 
2.39.5



  [text/x-patch] v6-0002-Replace-Latches-with-Interrupts.patch (221.5K, ../../[email protected]/3-v6-0002-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 68b598c84e094d223ef7fb61558a4e029acbb93c Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:51:54 +0200
Subject: [PATCH v6 2/5] Replace Latches with Interrupts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

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

This commit introduces two interrupt bits. INTERRUPT_GENERAL replaces
the general-purpose per-process latch. All code that previously set a
process's process latch now sets its INTERRUPT_GENERAL interrupt bit
instead.

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

More interrupt bits are planned for followup patches, to replace the
various ProcSignal bits, as well as ConfigReloadPending and
ShutdownRequestPending.

Reviewed-by: Thomas Munro, Andres Freund, Robert Haas, Álvaro Herrera
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 contrib/pg_prewarm/autoprewarm.c              |  20 +-
 contrib/postgres_fdw/connection.c             |  22 +-
 contrib/postgres_fdw/postgres_fdw.c           |   4 +-
 doc/src/sgml/bgworker.sgml                    |   2 +-
 doc/src/sgml/sources.sgml                     |   6 +-
 src/backend/access/heap/vacuumlazy.c          |  11 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/parallel.c         |  30 +-
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/access/transam/xlogfuncs.c        |  12 +-
 src/backend/access/transam/xlogrecovery.c     |  91 ++--
 src/backend/backup/basebackup_throttle.c      |  18 +-
 src/backend/commands/async.c                  |  19 +-
 src/backend/commands/vacuum.c                 |   4 +-
 src/backend/executor/nodeAppend.c             |   5 +-
 src/backend/executor/nodeGather.c             |   8 +-
 src/backend/libpq/auth.c                      |   9 +-
 src/backend/libpq/be-secure-gssapi.c          |  15 +-
 src/backend/libpq/be-secure-openssl.c         |   7 +-
 src/backend/libpq/be-secure.c                 |  29 +-
 src/backend/libpq/pqcomm.c                    |  29 +-
 src/backend/libpq/pqmq.c                      |   8 +-
 src/backend/postmaster/autovacuum.c           |  22 +-
 src/backend/postmaster/auxprocess.c           |   1 +
 src/backend/postmaster/bgworker.c             |  18 +-
 src/backend/postmaster/bgwriter.c             |  37 +-
 src/backend/postmaster/checkpointer.c         |  48 +--
 src/backend/postmaster/interrupt_handlers.c   |   6 +-
 src/backend/postmaster/pgarch.c               |  27 +-
 src/backend/postmaster/postmaster.c           |  31 +-
 src/backend/postmaster/startup.c              |  11 +-
 src/backend/postmaster/syslogger.c            |  18 +-
 src/backend/postmaster/walsummarizer.c        |  24 +-
 src/backend/postmaster/walwriter.c            |  23 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |  32 +-
 .../replication/logical/applyparallelworker.c |  39 +-
 src/backend/replication/logical/launcher.c    |  54 +--
 src/backend/replication/logical/slotsync.c    |  23 +-
 src/backend/replication/logical/tablesync.c   |  35 +-
 src/backend/replication/logical/worker.c      |  25 +-
 src/backend/replication/syncrep.c             |  29 +-
 src/backend/replication/walreceiver.c         |  47 ++-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           |  27 +-
 src/backend/storage/buffer/bufmgr.c           |  10 +-
 src/backend/storage/buffer/freelist.c         |  27 +-
 src/backend/storage/ipc/Makefile              |   2 +-
 src/backend/storage/ipc/interrupt.c           | 269 ++++++++++++
 src/backend/storage/ipc/latch.c               | 387 ------------------
 src/backend/storage/ipc/meson.build           |   2 +-
 src/backend/storage/ipc/procsignal.c          |   6 +-
 src/backend/storage/ipc/shm_mq.c              | 102 ++---
 src/backend/storage/ipc/signalfuncs.c         |  11 +-
 src/backend/storage/ipc/sinval.c              |  10 +-
 src/backend/storage/ipc/standby.c             |  22 +-
 src/backend/storage/ipc/waiteventset.c        | 384 +++++++++--------
 src/backend/storage/lmgr/condition_variable.c |  34 +-
 src/backend/storage/lmgr/predicate.c          |   8 +-
 src/backend/storage/lmgr/proc.c               | 127 +++---
 src/backend/storage/sync/sync.c               |   6 +-
 src/backend/tcop/postgres.c                   |  25 +-
 src/backend/utils/adt/misc.c                  |  26 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/init/globals.c              |   9 -
 src/backend/utils/init/miscinit.c             |  56 +--
 src/backend/utils/init/postinit.c             |  11 +-
 src/backend/utils/misc/timeout.c              |   8 +-
 src/backend/utils/mmgr/mcxt.c                 |   2 +-
 src/include/access/parallel.h                 |   2 +
 src/include/libpq/libpq-be-fe-helpers.h       |  45 +-
 src/include/libpq/libpq.h                     |   4 +-
 src/include/miscadmin.h                       |   4 -
 src/include/storage/interrupt.h               | 171 ++++++++
 src/include/storage/latch.h                   | 140 -------
 src/include/storage/proc.h                    |  16 +-
 src/include/storage/waiteventset.h            |  34 +-
 src/port/pgsleep.c                            |   8 +-
 src/test/isolation/isolationtester.c          |   2 +-
 src/test/modules/test_shm_mq/setup.c          |   9 +-
 src/test/modules/test_shm_mq/test.c           |  13 +-
 src/test/modules/test_shm_mq/worker.c         |   3 +-
 src/test/modules/worker_spi/worker_spi.c      |  20 +-
 src/tools/pgindent/typedefs.list              |   2 +-
 83 files changed, 1448 insertions(+), 1518 deletions(-)
 create mode 100644 src/backend/storage/ipc/interrupt.c
 delete mode 100644 src/backend/storage/ipc/latch.c
 create mode 100644 src/include/storage/interrupt.h
 delete mode 100644 src/include/storage/latch.h

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 1d7facf1f36..696cb458493 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -37,8 +37,8 @@
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/procsignal.h"
 #include "storage/smgr.h"
@@ -216,10 +216,10 @@ autoprewarm_main(Datum main_arg)
 		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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								 -1L,
+								 PG_WAIT_EXTENSION);
 		}
 		else
 		{
@@ -243,14 +243,14 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 delay_in_ms,
+								 PG_WAIT_EXTENSION);
 		}
 
 		/* Reset the latch, loop. */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	/*
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 8ef9702c05c..4c4f9ea84af 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -27,7 +27,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -802,8 +802,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(ERROR, NULL, conn, false, sql);
@@ -1446,7 +1446,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))
@@ -1541,7 +1541,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))
@@ -1654,12 +1654,12 @@ 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(1 << INTERRUPT_GENERAL,
+										   WL_INTERRUPT | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+										   PQsocket(conn),
+										   cur_timeout, pgfdw_we_cleanup_result);
+				ClearInterrupt(INTERRUPT_GENERAL);
 
 				CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 1131a8bf77e..31cd92f9e36 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7355,7 +7355,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 2c393385a91..3171054e55d 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -227,7 +227,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.
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index fa68d4d024a..fc642f5f713 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -989,19 +989,19 @@ 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_GENERAL);
 }
 </programlisting>
     </para>
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3b91d02605a..f8df18660ed 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -152,6 +152,7 @@
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
+#include "storage/interrupt.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
 #include "utils/lsyscache.h"
@@ -3242,11 +3243,11 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..7c29b23b01a 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 set 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/parallel.c b/src/backend/access/transam/parallel.c
index 94db1ec3012..52b1cbc2d95 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -34,6 +34,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/spin.h"
@@ -759,16 +760,16 @@ 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.  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(1 << INTERRUPT_GENERAL,
+								   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_GENERAL);
 			}
 		}
 
@@ -877,15 +878,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * or latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_GENERAL should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1038,7 +1040,7 @@ HandleParallelMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..4740dc89ef8 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -84,9 +84,9 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.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"
@@ -2664,7 +2664,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 		ProcNumber	walwriterProc = procglobal->walwriterProc;
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->walwriterProc);
 	}
 }
 
@@ -9382,11 +9382,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 8c3090165f0..ffe0237326b 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -28,7 +28,7 @@
 #include "pgstat.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/standby.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
@@ -718,17 +718,17 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a829a055a97..1e31b0b761a 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -52,9 +52,10 @@
 #include "replication/slotsync.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -316,23 +317,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.
 	 */
@@ -467,7 +451,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -541,13 +524,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).
@@ -1637,13 +1613,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);
 }
 
 /*
@@ -1803,7 +1772,7 @@ PerformWalRecovery(void)
 			}
 
 			/*
-			 * If we've been asked to lag the primary, wait on latch until
+			 * If we've been asked to lag the primary, wait on interrupt until
 			 * enough time has passed.
 			 */
 			if (recoveryApplyDelay(xlogreader))
@@ -3026,8 +2995,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)
@@ -3035,11 +3004,19 @@ recoveryApplyDelay(XLogReaderState *record)
 
 	while (true)
 	{
-		ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+		/*
+		 * INTERRUPT_GENERAL is used for all the usual interrupts, like config
+		 * reloads.  The wakeups when more WAL arrive use a different
+		 * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL
+		 * arriving don't wake up the startup process excessively when we're
+		 * waiting in other places, like for recovery conflicts.
+		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* This might change recovery_min_apply_delay. */
 		ProcessStartupProcInterrupts();
 
+		ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 		if (CheckForStandbyTrigger())
 			break;
 
@@ -3060,10 +3037,11 @@ 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(1 << INTERRUPT_RECOVERY_CONTINUE |
+							 1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 msecs,
+							 WAIT_EVENT_RECOVERY_APPLY_DELAY);
 	}
 	return true;
 }
@@ -3718,15 +3696,17 @@ 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(1 << INTERRUPT_RECOVERY_CONTINUE |
+											 1 << INTERRUPT_GENERAL,
+											 WL_INTERRUPT | WL_TIMEOUT |
+											 WL_EXIT_ON_PM_DEATH,
+											 wait_time,
+											 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
+						ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 						now = GetCurrentTimestamp();
 
 						/* Handle interrupt signals of startup process */
+						ClearInterrupt(INTERRUPT_GENERAL);
 						ProcessStartupProcInterrupts();
 					}
 					last_fail_time = now;
@@ -3993,11 +3973,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(1 << INTERRUPT_RECOVERY_CONTINUE |
+										 1 << INTERRUPT_GENERAL,
+										 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+										 -1L,
+										 WAIT_EVENT_RECOVERY_WAL_STREAM);
+					ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 					break;
 				}
 
@@ -4017,6 +3998,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 		 * This possibly-long loop needs to handle interrupts of startup
 		 * process.
 		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 		ProcessStartupProcInterrupts();
 	}
 
@@ -4491,7 +4473,10 @@ CheckPromoteSignal(void)
 void
 WakeupRecovery(void)
 {
-	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
+
+	if (procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_RECOVERY_CONTINUE, procno);
 }
 
 /*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index b2b743238f9..05443e0853a 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -17,7 +17,7 @@
 #include "backup/basebackup_sink.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,21 +163,21 @@ throttle(bbsink_throttle *sink, size_t increment)
 		if (sleep <= 0)
 			break;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
-		/* We're eating a potentially set latch, so check for interrupts */
+		/* We're eating a wakeup, so check for interrupts */
 		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be
 		 * the maximum time to sleep. Thus the cast to long is safe.
 		 */
-		wait_result = WaitLatch(MyLatch,
-								WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								(long) (sleep / 1000),
-								WAIT_EVENT_BASE_BACKUP_THROTTLE);
+		wait_result = WaitInterrupt(1 << INTERRUPT_GENERAL,
+									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									(long) (sleep / 1000),
+									WAIT_EVENT_BASE_BACKUP_THROTTLE);
 
-		if (wait_result & WL_LATCH_SET)
+		if (wait_result & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
 
 		/* Done waiting? */
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 4bd37d5beb5..201d7150083 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -91,7 +91,7 @@
  *	  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
+ *	  raises INTERRUPT_GENERAL, which triggers the event to be processed
  *	  immediately if this backend 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
@@ -140,6 +140,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/procsignal.h"
@@ -406,9 +407,9 @@ static NotificationList *pendingNotifies = NULL;
 /*
  * 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.
+ * notifyInterruptPending flag and raises the INTERRUPT_GENERAL interrupt.
+ * ProcessNotifyInterrupt() will then be called whenever it's safe to actually
+ * deal with the interrupt.
  */
 volatile sig_atomic_t notifyInterruptPending = false;
 
@@ -1812,7 +1813,7 @@ HandleNotifyInterrupt(void)
 	notifyInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1821,10 +1822,10 @@ HandleNotifyInterrupt(void)
  *		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.
+ *		HandleNotifyInterrupt() will cause the read to be interrupted with
+ *		INTERRUPT_GENERAL, and this routine will get called.  If we are truly
+ *		idle (ie, *not* inside a transaction block), process the incoming
+ *		notifies.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index e25ab97bc71..8017448de56 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2496,8 +2496,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 15c4227cc62..df4b5fe1627 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,8 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1042,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;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index dc7d1830259..5d0a58384b0 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -36,6 +36,7 @@
 #include "executor/tqueue.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "storage/interrupt.h"
 #include "utils/wait_event.h"
 
 
@@ -382,9 +383,10 @@ 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(1 << INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 81e2f8184e3..af282b3e802 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1653,8 +1653,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(hbaPort *port)
@@ -3088,8 +3089,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index a3d610b1373..d26805c9a81 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -21,6 +21,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
+#include "storage/interrupt.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 
@@ -414,7 +415,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.
  *
@@ -450,9 +451,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
@@ -489,7 +489,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
@@ -668,9 +668,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 64ff3ce3d6a..dcb640afba3 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -32,7 +32,8 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -522,8 +523,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 91576f94285..c61f0bdb4c0 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -28,7 +28,7 @@
 #include <arpa/inet.h>
 
 #include "libpq/libpq.h"
-#include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -213,7 +213,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,12 +228,12 @@ 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,
@@ -241,9 +241,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientReadInterrupt(true);
 
 			/*
@@ -284,7 +284,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;
@@ -338,7 +339,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -350,9 +351,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientWriteInterrupt(true);
 
 			/*
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index e5171467de1..daf7c7435bb 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -77,6 +77,7 @@
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
@@ -175,7 +176,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(sizeof(Port));
@@ -287,8 +288,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
@@ -306,18 +307,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,
+									  1 << INTERRUPT_GENERAL, 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;
 }
@@ -2063,7 +2064,7 @@ 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);
 
 retry:
 	rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0);
@@ -2071,15 +2072,15 @@ retry:
 	{
 		if (events[i].events & WL_SOCKET_CLOSED)
 			return false;
-		if (events[i].events & WL_LATCH_SET)
+		if (events[i].events & WL_INTERRUPT)
 		{
 			/*
-			 * A latch event might be preventing other events from being
+			 * An interrupt event might be preventing other events from being
 			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect latches to survive across
-			 * CHECK_FOR_INTERRUPTS().
+			 * because no code should expect INTERRUPT_GENERAL to survive
+			 * across CHECK_FOR_INTERRUPTS().
 			 */
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			goto retry;
 		}
 	}
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index f1a08bc32ca..ef080f63112 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -20,6 +20,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 
@@ -181,9 +182,10 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_GENERAL);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index cdbdc0f3b80..e27da9fd88a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -89,8 +89,8 @@
 #include "postmaster/interrupt_handlers.h"
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -575,24 +575,24 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
 		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * signal handlers will wake us by calling RaiseInterrupt).
 		 */
-		(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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessAutoVacLauncherInterrupts();
 
@@ -1359,7 +1359,7 @@ static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 4f6795f7265..61bcac8d9f8 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -23,6 +23,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 116ddf7b835..c17b8db545f 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -21,8 +21,8 @@
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -1226,9 +1226,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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1236,7 +1236,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
@@ -1269,9 +1269,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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1279,7 +1279,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 4d2165fa998..a37257218b1 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -42,6 +42,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -224,7 +225,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		int			rc;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessMainLoopInterrupts();
 
@@ -299,22 +300,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(1 << INTERRUPT_GENERAL,
+						   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
-		 * 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.
+		 * If no interrupt event 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
+		 * 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
@@ -329,10 +330,10 @@ 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(1 << INTERRUPT_GENERAL,
+								 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 e3759a7a80b..da6caa64457 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -52,6 +52,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
@@ -352,7 +353,7 @@ 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_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -534,7 +535,7 @@ 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)
@@ -572,10 +573,10 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout * 1000L /* convert to ms */ ,
+							 WAIT_EVENT_CHECKPOINTER_MAIN);
 	}
 
 	/*
@@ -613,17 +614,17 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	for (;;)
 	{
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessCheckpointerInterrupts();
 
 		if (ShutdownRequestPending)
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-						 0,
-						 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
 	}
 
 	/* Normal exit from the checkpointer is here */
@@ -804,10 +805,11 @@ 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(1 << INTERRUPT_GENERAL,
+					  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
+					  100,
+					  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 	else if (--absorb_counter <= 0)
 	{
@@ -916,7 +918,7 @@ static void
 ReqShutdownXLOG(SIGNAL_ARGS)
 {
 	ShutdownXLOGPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
@@ -1033,14 +1035,14 @@ 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
+	 * Send INTERRUPT_GENERAL 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 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().
+	 * 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++)
@@ -1059,7 +1061,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1186,7 +1188,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->checkpointerProc);
 	}
 
 	return true;
diff --git a/src/backend/postmaster/interrupt_handlers.c b/src/backend/postmaster/interrupt_handlers.c
index 14fd74b46d3..10a4710f403 100644
--- a/src/backend/postmaster/interrupt_handlers.c
+++ b/src/backend/postmaster/interrupt_handlers.c
@@ -18,8 +18,8 @@
 
 #include "miscadmin.h"
 #include "postmaster/interrupt_handlers.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
@@ -61,7 +61,7 @@ void
 SignalHandlerForConfigReload(SIGNAL_ARGS)
 {
 	ConfigReloadPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -105,5 +105,5 @@ void
 SignalHandlerForShutdownRequest(SIGNAL_ARGS)
 {
 	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 1cd2ae87e18..1e921c0e651 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -41,8 +41,8 @@
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -247,8 +247,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;
 
@@ -282,13 +282,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(&ProcGlobal->allProcs[arch_pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, arch_pgprocno);
 }
 
 
@@ -298,7 +297,7 @@ pgarch_waken_stop(SIGNAL_ARGS)
 {
 	/* set flag to do a final cycle and shut down afterwards */
 	ready_to_stop = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -318,7 +317,7 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
 		time_to_stop = ready_to_stop;
@@ -355,10 +354,10 @@ 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(1 << INTERRUPT_GENERAL,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d2a7a7add6f..a72e280845c 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -109,6 +109,7 @@
 #include "replication/slotsync.h"
 #include "replication/walsender.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "tcop/backend_startup.h"
@@ -549,7 +550,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
@@ -1620,14 +1620,14 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
-					  NULL);
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  1 << INTERRUPT_GENERAL, NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1656,19 +1656,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_GENERAL);
 
 			/*
 			 * 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)
@@ -1961,7 +1962,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1971,7 +1972,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2048,7 +2049,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2209,7 +2210,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 27e86cf393f..f2ac862513b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -28,6 +28,7 @@
 #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"
@@ -40,7 +41,7 @@
  * 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().
  * 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
@@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
 	/* Shutdown the recovery environment */
 	if (standbyState != STANDBY_DISABLED)
 		ShutdownRecoveryTransactionEnvironment();
+
+	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
 }
 
 
@@ -220,6 +223,12 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	MyBackendType = B_STARTUP;
 	AuxiliaryProcessMainCommon();
 
+	/*
+	 * Advertise our proc number so that backends can wake us up, when the
+	 * server is promoted or recovery is paused/resumed.
+	 */
+	ProcGlobal->startupProc = MyProcNumber;
+
 	/* Arrange to clean up at startup process exit */
 	on_shmem_exit(StartupProcExit, 0);
 
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 2a61a8b766b..803dcde071b 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -44,8 +44,8 @@
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -328,7 +328,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
@@ -338,9 +338,9 @@ 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, 1 << INTERRUPT_GENERAL, 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 */
@@ -356,7 +356,7 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 #endif
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -1188,7 +1188,7 @@ pipeThread(void *arg)
 				 ftello(csvlogFile) >= Log_RotationSize * (pgoff_t) 1024) ||
 				(jsonlogFile != NULL &&
 				 ftello(jsonlogFile) >= Log_RotationSize * (pgoff_t) 1024))
-				SetLatch(MyLatch);
+				RaiseInterrupt(INTERRUPT_GENERAL);
 		}
 		LeaveCriticalSection(&sysloggerSection);
 	}
@@ -1199,8 +1199,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_GENERAL);
 
 	LeaveCriticalSection(&sysloggerSection);
 	_endthread();
@@ -1595,5 +1595,5 @@ static void
 sigUsr1Handler(SIGNAL_ARGS)
 {
 	rotation_requested = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 3184961462a..9288a1b299a 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -39,8 +39,8 @@
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -316,10 +316,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) */
@@ -631,8 +629,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)
@@ -647,7 +645,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&ProcGlobal->allProcs[pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, pgprocno);
 }
 
 /*
@@ -1641,11 +1639,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(1 << INTERRUPT_GENERAL,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_GENERAL);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index de16ac4a956..9678c01c5a3 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -54,6 +54,7 @@
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -222,12 +223,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))
 		{
@@ -236,7 +237,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		}
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* Process any signals received recently */
 		ProcessMainLoopInterrupts();
@@ -263,9 +264,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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 1b158c9d288..41137b4d373 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -30,7 +30,7 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
@@ -237,16 +237,16 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical,
 		else
 			io_flag = WL_SOCKET_WRITEABLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-							   PQsocket(conn->streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
@@ -848,17 +848,17 @@ libpqrcv_PQgetResult(PGconn *streamConn)
 		 * since we'll get interrupted by signals and can handle any
 		 * interrupts here.
 		 */
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-							   WL_LATCH_SET,
-							   PQsocket(streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+								   WL_INTERRUPT,
+								   PQsocket(streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 4d2d3fd2c56..5d27b4d5886 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -165,6 +165,7 @@
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "tcop/tcopprot.h"
@@ -804,13 +805,13 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 				int			rc;
 
 				/* Wait for more work. */
-				rc = WaitLatch(MyLatch,
-							   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   1000L,
-							   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
+				rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+								   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   1000L,
+								   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
 
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				if (rc & WL_INTERRUPT)
+					ClearInterrupt(INTERRUPT_GENERAL);
 			}
 		}
 		else
@@ -990,7 +991,7 @@ HandleParallelApplyMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1182,14 +1183,14 @@ 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(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   SHM_SEND_RETRY_INTERVAL_MS,
+						   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1254,13 +1255,13 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo,
 			break;
 
 		/* Wait to be signalled. */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 10L,
-						 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L,
+							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* 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 434bb1f6f62..6578c83194b 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -34,6 +34,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -209,16 +210,17 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
@@ -544,13 +546,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -585,13 +587,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -667,7 +669,7 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 }
 
 /*
- * Wake up (using latch) any logical replication worker for specified sub/rel.
+ * Wake up (using interrupt) any logical replication worker for specified sub/rel.
  */
 void
 logicalrep_worker_wakeup(Oid subid, Oid relid)
@@ -685,7 +687,7 @@ logicalrep_worker_wakeup(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.
  */
@@ -694,7 +696,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -1212,14 +1214,14 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   wait_time,
-					   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   wait_time,
+						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 44cc5c6fc37..505cf21c143 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,6 +59,7 @@
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -1252,13 +1253,13 @@ 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);
+	rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1588,13 +1589,13 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 6af5c9fe16c..877fb7aca36 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -111,6 +111,7 @@
 #include "replication/slot.h"
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -210,11 +211,11 @@ wait_for_relation_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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return false;
@@ -260,15 +261,15 @@ 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(1 << INTERRUPT_GENERAL,
+						   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_GENERAL);
 	}
 
 	return false;
@@ -555,7 +556,7 @@ process_syncing_tables_for_apply(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
 						 */
 						CommitTransactionCommand();
 						pgstat_report_stat(false);
@@ -771,14 +772,14 @@ 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);
+		(void) WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+									 WL_SOCKET_READABLE | WL_INTERRUPT |
+									 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return bytesread;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 67d0647aa32..29d03af0ac5 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -177,6 +177,7 @@
 #include "replication/worker_internal.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/buffile.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "tcop/tcopprot.h"
@@ -3742,26 +3743,26 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
 		else
 			wait_time = NAPTIME_PER_CYCLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index d75e3968035..1c0d79b8568 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -81,6 +81,7 @@
 #include "replication/syncrep.h"
 #include "replication/walsender.h"
 #include "replication/walsender_private.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc_hooks.h"
@@ -222,22 +223,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -282,11 +283,13 @@ 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 interrupt, so no need for timeout.
 		 */
-		rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1,
-					   WAIT_EVENT_SYNC_REP);
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH,
+						   -1,
+						   WAIT_EVENT_SYNC_REP);
 
 		/*
 		 * If the postmaster dies, we'll probably never get an acknowledgment,
@@ -902,7 +905,7 @@ SyncRepWakeQueue(bool all, int mode)
 		/*
 		 * Wake only when we have set state and removed from queue.
 		 */
-		SetLatch(&(proc->procLatch));
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 		numprocs++;
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7392e89767d..4bc23ae21a3 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -67,6 +67,7 @@
 #include "postmaster/interrupt_handlers.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -146,17 +147,17 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
 static void WalRcvComputeNextWakeup(WalRcvWakeupReason reason, TimestampTz now);
 
 /*
- * Process any interrupts the walreceiver process may have received.
- * This should be called any time the process's latch has become set.
+ * Process any interrupts the walreceiver process may have received.  This
+ * should be called any time the INTERRUPT_GENERAL interrupt has become set.
  *
  * Currently, only SIGTERM is of interest.  We can't just exit(1) within the
  * SIGTERM signal handler, because the signal might arrive in the middle of
  * some critical operation, like while we're holding a spinlock.  Instead, the
- * signal handler sets a flag variable as well as setting the process's latch.
+ * signal handler sets a flag variable as well as raising INTERRUPT_GENERAL.
  * We must check the flag (by calling ProcessWalRcvInterrupts) anytime the
- * latch has become set.  Operations that could block for a long time, such as
- * reading from a remote server, must pay attention to the latch too; see
- * libpqrcv_PQgetResult for example.
+ * INTERRUPT_GENERAL has become set.  Operations that could block for a long
+ * time, such as reading from a remote server, must pay attention to the
+ * interrupt too; see libpqrcv_PQgetResult for example.
  */
 void
 ProcessWalRcvInterrupts(void)
@@ -536,25 +537,25 @@ 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(1 << INTERRUPT_GENERAL,
+										   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_GENERAL);
 					ProcessWalRcvInterrupts();
 
 					if (walrcv->force_reply)
@@ -702,7 +703,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessWalRcvInterrupts();
 
@@ -734,8 +735,10 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
 	if (update_process_title)
@@ -1376,7 +1379,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 8de2886ff0b..4f7522008fc 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -26,6 +26,7 @@
 #include "access/xlogrecovery.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
+#include "storage/interrupt.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
@@ -317,7 +318,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_GENERAL, walrcv_proc);
 }
 
 /*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index ecd96ad7dbc..4cafa69b89c 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -80,6 +80,7 @@
 #include "replication/walsender_private.h"
 #include "storage/condition_variable.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -1013,8 +1014,8 @@ 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
+ * Inside the walsender we can do better than read_local_xlog_page, which
+ * has to do a plain sleep/busy loop, because the walsender's interrupt gets
  * set every time WAL is flushed.
  */
 static int
@@ -1611,7 +1612,7 @@ ProcessPendingWrites(void)
 				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1628,8 +1629,8 @@ ProcessPendingWrites(void)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1817,7 +1818,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		long		sleeptime;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1937,8 +1938,8 @@ WalSndWaitForWal(XLogRecPtr loc)
 		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	return RecentFlushPtr;
 }
 
@@ -2754,7 +2755,7 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	for (;;)
 	{
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -3553,7 +3554,7 @@ static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* Set up signal handlers */
@@ -3659,7 +3660,7 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
@@ -3671,8 +3672,8 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 * 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().
+	 * 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
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7915ed624c1..c5bf4a70925 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -51,6 +51,7 @@
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -2880,7 +2881,7 @@ UnpinBufferNoOwner(BufferDesc *buf)
 
 				buf_state &= ~BM_PIN_COUNT_WAITER;
 				UnlockBufHdr(buf, buf_state);
-				ProcSendSignal(wait_backend_pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, wait_backend_pgprocno);
 			}
 			else
 				UnlockBufHdr(buf, buf_state);
@@ -5283,7 +5284,12 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+		{
+			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_PIN);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index 336715b6c63..02b223717db 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -19,6 +19,7 @@
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 
 #define INT_ACCESS_ONCE(var)	((int)(*((volatile int *)&(var))))
@@ -219,27 +220,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *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(&ProcGlobal->allProcs[bgwprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, bgwprocno);
 	}
 
 	/*
@@ -420,10 +419,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..5c7c72f902a 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -13,9 +13,9 @@ OBJS = \
 	dsm.o \
 	dsm_impl.o \
 	dsm_registry.o \
+	interrupt.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/interrupt.c b/src/backend/storage/ipc/interrupt.c
new file mode 100644
index 00000000000..4d0edddcf99
--- /dev/null
+++ b/src/backend/storage/ipc/interrupt.c
@@ -0,0 +1,269 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/storage/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "storage/interrupt.h"
+#include "storage/proc.h"
+#include "storage/waiteventset.h"
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint32 LocalPendingInterrupts;
+
+pg_atomic_uint32 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch backx.
+	 */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ */
+void
+RaiseInterrupt(InterruptType reason)
+{
+	uint32		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << reason);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ */
+void
+SendInterrupt(InterruptType reason, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint32		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, 1 << reason);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(uint32 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(uint32 interruptMask, 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_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;
+}
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index c6aefd2f688..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,387 +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-2025, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-	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 b1b73dac3be..8132b57c8b9 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -5,9 +5,9 @@ backend_sources += files(
   'dsm.c',
   'dsm_impl.c',
   'dsm_registry.c',
+  'interrupt.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7d201965503..91557a7927e 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -25,8 +25,8 @@
 #include "replication/logicalworker.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -483,7 +483,7 @@ HandleProcSignalBarrierInterrupt(void)
 {
 	InterruptPending = true;
 	ProcSignalBarrierPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* interrupt will be raised by procsignal_sigusr1_handler */
 }
 
 /*
@@ -714,7 +714,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
 		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
 
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 2c79a649f46..b752dccee1d 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.
  *
@@ -22,6 +22,7 @@
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/bgworker.h"
+#include "storage/interrupt.h"
 #include "storage/shm_mq.h"
 #include "storage/spin.h"
 #include "utils/memutils.h"
@@ -44,10 +45,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 SendIntterupt/ClearInterrupt, do we need to add some?
  *
  * mq_ring_size and mq_ring_offset never change after initialization, and
  * can therefore be read without the lock.
@@ -112,7 +114,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
@@ -214,7 +216,7 @@ shm_mq_set_receiver(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (sender != NULL)
-		SetLatch(&sender->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
@@ -232,7 +234,7 @@ shm_mq_set_sender(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (receiver != NULL)
-		SetLatch(&receiver->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(receiver));
 }
 
 /*
@@ -341,14 +343,14 @@ 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 on our process interrupt when the ring
+ * buffer fills up, and then continue writing once the receiver has drained
+ * some data.  The process interrupt is cleared after each wait.
  *
- * When nowait = true, we do not manipulate the state of the process latch;
+ * When nowait = true, we do not manipulate the state of the process interrupt;
  * instead, if the buffer becomes full, we return SHM_MQ_WOULD_BLOCK.  In
  * this case, the caller should call this function again, with the same
- * arguments, each time the process latch is set.  (Once begun, the sending
+ * arguments, each time the process interrupt is set.  (Once begun, the sending
  * of a message cannot be aborted except by detaching from the queue; changing
  * the length or payload will corrupt the queue.)
  *
@@ -539,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_GENERAL, GetNumberFromPGProc(receiver));
 		mqh->mqh_send_pending = 0;
 	}
 
@@ -557,16 +559,16 @@ 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
+ * When nowait = false, we'll wait on our process interrupt when the ring
+ * buffer is empty and we have not yet received a full message.  The sender
+ * will set our process interrupt after more data has been written, and we'll
+ * resume processing.  Each call will therefore return a complete message
  * (unless the sender detaches the queue).
  *
- * When nowait = 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 manipulate the state of the process
+ * interrupt; instead, whenever the buffer is empty and we need to read from
+ * it, we return SHM_MQ_WOULD_BLOCK.  In this case, the caller should call
+ * this function again after the process interrupt has been set.
  */
 shm_mq_result
 shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
@@ -619,8 +621,8 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
 	 * If we've consumed an amount of data greater than 1/4th of the ring
 	 * size, mark it consumed in shared memory.  We try to avoid doing this
 	 * unnecessarily when only a small amount of data has been consumed,
-	 * because SetLatch() is fairly expensive and we don't want to do it too
-	 * often.
+	 * because SendInterrupt() is fairly expensive and we don't want to do it
+	 * too often.
 	 */
 	if (mqh->mqh_consume_pending > mq->mq_ring_size / 4)
 	{
@@ -895,7 +897,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +995,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 * Therefore, we can read it without acquiring the spinlock.
 			 */
 			Assert(mqh->mqh_counterparty_attached);
-			SetLatch(&mq->mq_receiver->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(mq->mq_receiver));
 
 			/*
 			 * We have just updated the mqh_send_pending bytes in the shared
@@ -1001,7 +1003,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 */
 			mqh->mqh_send_pending = 0;
 
-			/* Skip manipulation of our latch if nowait = true. */
+			/* Skip waiting if nowait = true. */
 			if (nowait)
 			{
 				*bytes_written = sent;
@@ -1009,17 +1011,17 @@ 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(1 << INTERRUPT_GENERAL, 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_GENERAL);
 
 			/* An interrupt may have occurred while we were waiting. */
 			CHECK_FOR_INTERRUPTS();
@@ -1054,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.
 			 */
@@ -1150,22 +1152,22 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1250,11 +1252,11 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1293,7 +1295,7 @@ shm_mq_inc_bytes_read(shm_mq *mq, Size n)
 	 */
 	sender = mq->mq_sender;
 	Assert(sender != NULL);
-	SetLatch(&sender->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bebc97ecffd..d77b4f44ad5 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -20,6 +20,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
+#include "storage/interrupt.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -204,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 6eea8e87169..3c112daf001 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -16,7 +16,7 @@
 
 #include "access/xact.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
@@ -31,8 +31,8 @@ uint64		SharedInvalidMessageCounter;
  * 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
+ * The signal handler will set an interrupt pending flag and raise the
+ * INTERRUPT_GENERAL. Whenever starting to read from the client, or when
  * interrupted while doing so, ProcessClientReadInterrupt() will call
  * ProcessCatchupEvent().
  */
@@ -148,7 +148,7 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
  *
  * 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.
+ * raising INTERRUPT_GENERAL.
  */
 void
 HandleCatchupInterrupt(void)
@@ -161,7 +161,7 @@ HandleCatchupInterrupt(void)
 	catchupInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 5acb4508f85..76b2996f327 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -26,6 +26,7 @@
 #include "pgstat.h"
 #include "replication/slot.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/sinvaladt.h"
@@ -594,7 +595,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
@@ -696,7 +697,10 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  PG_WAIT_LOCK | locktag.locktag_type);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	/*
 	 * Exit if ltime is reached. Then all the backends holding conflicting
@@ -745,7 +749,10 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+					  PG_WAIT_LOCK | locktag.locktag_type);
+		ClearInterrupt(INTERRUPT_GENERAL);
+		CHECK_FOR_INTERRUPTS();
 	}
 
 cleanup:
@@ -837,9 +844,14 @@ 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
+	 * the signal handler??
 	 */
-	ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+	WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  WAIT_EVENT_BUFFER_PIN);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	if (got_standby_delay_timeout)
 		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 7c0e66900f9..1b7b3926864 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
@@ -70,9 +71,10 @@
 #include "portability/instr_time.h"
 #include "postmaster/postmaster.h"
 #include "storage/fd.h"
+#include "storage/interrupt.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"
@@ -127,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 doing syscalls related to waiting.
 	 */
-	Latch	   *latch;
-	int			latch_pos;
+	uint32		interrupt_mask;
+	int			interrupt_pos;
 
 	/*
 	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
@@ -166,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -182,9 +184,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
 
@@ -234,7 +243,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)
@@ -284,12 +293,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");
@@ -310,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -348,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -411,7 +426,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;
 
@@ -496,9 +512,9 @@ FreeWaitEventSet(WaitEventSet *set)
 		 cur_event < (set->events + set->nevents);
 		 cur_event++)
 	{
-		if (cur_event->events & WL_LATCH_SET)
+		if (cur_event->events & WL_INTERRUPT)
 		{
-			/* uses the latch's HANDLE */
+			/* uses the process's wakeup HANDLE */
 		}
 		else if (cur_event->events & WL_POSTMASTER_DEATH)
 		{
@@ -535,7 +551,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
 
 /* ---
  * Add an event to the set. Possible events are:
- * - WL_LATCH_SET: Wait for the latch to be set
+ * - WL_INTERRUPT: Wait for the interrupt to be set
  * - 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
@@ -553,10 +569,6 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * Returns the offset in WaitEventSet->events (starting from 0), which can be
  * used to modify previously added wait events using ModifyWaitEvent().
  *
- * In the WL_LATCH_SET case the latch must be owned by the current process,
- * i.e. it must be a process-local latch initialized with InitLatch, or a
- * shared latch associated with the current process by calling OwnLatch.
- *
  * In the WL_SOCKET_READABLE/WRITEABLE/CONNECTED/ACCEPT cases, EOF and error
  * conditions cause the socket to be reported as readable/writable/connected,
  * so that the caller can deal with the condition.
@@ -566,7 +578,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, uint32 interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +592,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 */
@@ -608,10 +617,10 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 	event->reset = false;
 #endif
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		set->latch = latch;
-		set->latch_pos = event->pos;
+		set->interrupt_mask = interruptMask;
+		set->interrupt_pos = event->pos;
 #if defined(WAIT_USE_SELF_PIPE)
 		event->fd = selfpipe_readfd;
 #elif defined(WAIT_USE_SIGNALFD)
@@ -645,14 +654,14 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 }
 
 /*
- * Change the event mask and, in the WL_LATCH_SET case, the latch associated
- * with the WaitEvent.  The latch may be changed to NULL to disable the latch
- * temporarily, and then set back to a latch later.
+ * Change the event mask and, in the WL_INTERRUPT case, the interrupt mask
+ * associated with the WaitEvent.  The interrupt mask may be changed to 0 to
+ * disable the wakeups on interrupts temporarily, and then set back later.
  *
  * 'pos' is the id returned by AddWaitEventToSet.
  */
 void
-ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
+ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, uint32 interruptMask)
 {
 	WaitEvent  *event;
 #if defined(WAIT_USE_KQUEUE)
@@ -688,34 +697,28 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +748,8 @@ WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
 	epoll_ev.events = EPOLLERR | EPOLLHUP;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		epoll_ev.events |= EPOLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -794,9 +796,8 @@ WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
 	pollfd->fd = event->fd;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		pollfd->events = POLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -858,9 +859,9 @@ WaitEventAdjustKqueueAddPostmaster(struct kevent *k_ev, WaitEvent *event)
 }
 
 static inline void
-WaitEventAdjustKqueueAddLatch(struct kevent *k_ev, WaitEvent *event)
+WaitEventAdjustKqueueAddInterruptWakeup(struct kevent *k_ev, WaitEvent *event)
 {
-	/* For now latch can only be added, not removed. */
+	/* For now interrupt wakeup can only be added, not removed. */
 	k_ev->ident = SIGURG;
 	k_ev->filter = EVFILT_SIGNAL;
 	k_ev->flags = EV_ADD;
@@ -886,8 +887,7 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 	if (old_events == event->events)
 		return;
 
-	Assert(event->events != WL_LATCH_SET || set->latch != NULL);
-	Assert(event->events == WL_LATCH_SET ||
+	Assert(event->events == WL_INTERRUPT ||
 		   event->events == WL_POSTMASTER_DEATH ||
 		   (event->events & (WL_SOCKET_READABLE |
 							 WL_SOCKET_WRITEABLE |
@@ -902,10 +902,10 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 		 */
 		WaitEventAdjustKqueueAddPostmaster(&k_ev[count++], event);
 	}
-	else if (event->events == WL_LATCH_SET)
+	else if (event->events == WL_INTERRUPT)
 	{
-		/* We detect latch wakeup using a signal event. */
-		WaitEventAdjustKqueueAddLatch(&k_ev[count++], event);
+		/* We detect interrupt wakeup using a signal event. */
+		WaitEventAdjustKqueueAddInterruptWakeup(&k_ev[count++], event);
 	}
 	else
 	{
@@ -983,10 +983,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)
 	{
@@ -1042,6 +1045,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,64 +1067,57 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		uint32		old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
 			 * zero timeout to see what non-latch events we can fit into the
@@ -1129,34 +1126,58 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 			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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) 1 << SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1227,16 +1248,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1389,13 +1410,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1511,16 +1532,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1619,6 +1640,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
@@ -1724,19 +1754,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1781,7 +1807,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
@@ -1887,18 +1913,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)
 {
@@ -1915,7 +1940,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;
@@ -2002,14 +2027,13 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
  * NB: 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.) XXX
-  *
+ *
  * NB: this function is called from critical sections and signal handlers so
  * throwing an error is not a good idea.
  *
@@ -2018,6 +2042,7 @@ ResOwnerReleaseWaitEventSet(Datum res)
 void
 WakeupMyProc(void)
 {
+#ifndef WIN32
 #if defined(WAIT_USE_SELF_PIPE)
 	if (waiting)
 		sendSelfPipeByte();
@@ -2025,12 +2050,23 @@ 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);
-}
+#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 228303e77f7..ef792103835 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -21,6 +21,7 @@
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/proclist.h"
 #include "storage/spin.h"
@@ -147,23 +148,23 @@ 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(1 << INTERRUPT_GENERAL, 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_GENERAL);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -176,9 +177,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))
@@ -266,9 +268,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_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -297,8 +299,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
@@ -331,7 +333,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -355,6 +357,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 5b21a053981..11ae421ad68 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -207,6 +207,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
+#include "storage/interrupt.h"
 #include "storage/predicate.h"
 #include "storage/predicate_internals.h"
 #include "storage/proc.h"
@@ -1576,7 +1577,10 @@ GetSafeSnapshot(Snapshot origSnapshot)
 				 SxactIsROUnsafe(MySerializableXact)))
 		{
 			LWLockRelease(SerializableXactHashLock);
-			ProcWaitForSignal(WAIT_EVENT_SAFE_SNAPSHOT);
+			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_SAFE_SNAPSHOT);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
 			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 		}
 		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
@@ -3609,7 +3613,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe)
 			 */
 			if (SxactIsDeferrableWaiting(roXact) &&
 				(SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact)))
-				ProcSendSignal(roXact->pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, roXact->pgprocno);
 		}
 	}
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 749a79d48ef..34a525ae607 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -42,6 +42,7 @@
 #include "replication/slotsync.h"
 #include "replication/syncrep.h"
 #include "storage/condition_variable.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -258,14 +259,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -479,13 +494,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);
@@ -649,13 +659,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -932,21 +937,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1009,13 +1013,12 @@ AuxiliaryProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/* look at the equivalent ProcKill() code for comments */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	SpinLockAcquire(ProcStructLock);
 
@@ -1342,18 +1345,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
 	{
@@ -1399,9 +1402,9 @@ 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
@@ -1693,7 +1696,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 links invalid.
  *
@@ -1722,7 +1725,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1874,45 +1877,19 @@ 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.
+	 * 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.
 	 *
 	 * Note that, when this function runs inside procsignal_sigusr1_handler(),
-	 * the handler function sets the latch again after the latch is set here.
+	 * the handler function raises the interrupt again after the interrupt is
+	 * raised here.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	errno = save_errno;
 }
 
-/*
- * 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(&ProcGlobal->allProcs[procNumber].procLatch);
-}
-
 /*
  * BecomeLockGroupLeader - designate process as lock group leader
  *
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index fc16db90133..4bb8646c23f 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -27,7 +27,7 @@
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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/postgres.c b/src/backend/tcop/postgres.c
index 60ab8cd40c7..9a8db57195e 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -61,6 +61,7 @@
 #include "replication/walsender.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -519,15 +520,15 @@ ProcessClientReadInterrupt(bool blocked)
 		/*
 		 * 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
+		 * make sure the interrupt flag is set, so that if there is no data
 		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while reading.
 		 */
 		if (blocked)
 			CHECK_FOR_INTERRUPTS();
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -553,9 +554,9 @@ ProcessClientWriteInterrupt(bool blocked)
 		 * 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
+		 * sure the interrupt flag is set, so that if the write would block
 		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while writing.
 		 */
 		if (blocked)
@@ -579,7 +580,7 @@ ProcessClientWriteInterrupt(bool blocked)
 			}
 		}
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -3012,12 +3013,12 @@ 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);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on 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.
 	 */
@@ -3041,8 +3042,8 @@ StatementCancelHandler(SIGNAL_ARGS)
 		QueryCancelPending = true;
 	}
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* signal handler for floating point exception */
@@ -3068,7 +3069,7 @@ HandleRecoveryConflictInterrupt(ProcSignalReason reason)
 	RecoveryConflictPendingReasons[reason] = true;
 	RecoveryConflictPending = true;
 	InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 6fcfd031428..160ea59d94b 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -38,7 +38,7 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -373,16 +373,16 @@ pg_sleep(PG_FUNCTION_ARGS)
 	float8		endtime;
 
 	/*
-	 * 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.
 	 */
 #define GetNowFloat()	((float8) GetCurrentTimestamp() / 1000000.0)
 
@@ -403,11 +403,11 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 delay_ms,
+							 WAIT_EVENT_PG_SLEEP);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 9682f9dbdca..e4d145b0529 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1738,10 +1738,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/init/globals.c b/src/backend/utils/init/globals.c
index b844f9fdaef..79bc999cf26 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -52,15 +52,6 @@ bool		MyCancelKeyValid = false;
 int32		MyCancelKey = 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 34b8450b53c..bd0f566612b 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -42,8 +42,8 @@
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,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
  *
@@ -126,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
@@ -187,10 +184,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.
@@ -211,48 +207,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 ee1a9d5d98b..e54b5b39396 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -45,6 +45,7 @@
 #include "replication/walsender.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
@@ -1383,7 +1384,7 @@ TransactionTimeoutHandler(void)
 {
 	TransactionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1391,7 +1392,7 @@ IdleInTransactionSessionTimeoutHandler(void)
 {
 	IdleInTransactionSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1399,7 +1400,7 @@ IdleSessionTimeoutHandler(void)
 {
 	IdleSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1407,7 +1408,7 @@ IdleStatsUpdateTimeoutHandler(void)
 {
 	IdleStatsUpdateTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1415,7 +1416,7 @@ ClientCheckTimeoutHandler(void)
 {
 	CheckClientConnectionPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index d92efa12550..93994190c71 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,7 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -371,10 +371,10 @@ handle_sig_alarm(SIGNAL_ARGS)
 	HOLD_INTERRUPTS();
 
 	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
+	 * SIGALRM is always cause for waking anything waiting on
+	 * INTERRUPT_GENERAL.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 91060de0ab7..5233a65aaa0 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1273,7 +1273,7 @@ HandleLogMemoryContextInterrupt(void)
 {
 	InterruptPending = true;
 	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index f37be6d5690..a257d0a8ee6 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -14,6 +14,8 @@
 #ifndef PARALLEL_H
 #define PARALLEL_H
 
+#include <signal.h>
+
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 16205b824fa..f88ae8137d6 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -43,7 +43,7 @@
 #include "libpq-fe.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -177,8 +177,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -209,16 +209,16 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
+			if (rc & WL_INTERRUPT)
 			{
-				ResetLatch(MyLatch);
+				ClearInterrupt(INTERRUPT_GENERAL);
 				CHECK_FOR_INTERRUPTS();
 			}
 
@@ -341,17 +341,17 @@ 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(1 << INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -407,7 +407,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)
@@ -436,10 +436,11 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
+			WaitInterruptOrSocket(1 << INTERRUPT_GENERAL, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index aeb66ca40cf..6de03ce1163 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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/miscadmin.h b/src/include/miscadmin.h
index a2b63495eec..f5b773b79e5 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -190,7 +190,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT bool MyCancelKeyValid;
 extern PGDLLIMPORT int32 MyCancelKey;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -323,9 +322,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/storage/interrupt.h b/src/include/storage/interrupt.h
new file mode 100644
index 00000000000..779d61a8f07
--- /dev/null
+++ b/src/include/storage/interrupt.h
@@ -0,0 +1,171 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process 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.
+ *
+ * Interrupt flags can be "raised" synchronously by code that wants to defer
+ * an action, or asynchronously by timer signal handlers, other signal
+ * handlers or "sent" by other backends setting them directly.
+ *
+ * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is
+ * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query
+ * cancellation or idle session timeout. Well behaved backend code performs
+ * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never
+ * sleep using mechanisms other than the WaitEventSet mechanism or the more
+ * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for
+ * bounded short periods, eg LWLock waits), so they should react in good time.
+ *
+ * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
+ * consists of tasks that are safe to perform at most times.  They can be
+ * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
+ *
+ *
+ * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
+ *
+ * for (;;)
+ * {
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ *	   if (work to do)
+ *		   Do Stuff();
+ *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ * }
+ *
+ * It's important to clear the interrupt *before* checking if there's work to
+ * do. Otherwise, if someone sets the interrupt between the check and the
+ * ClearInterrupt() 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
+ *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ * }
+ *
+ * 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.
+ *
+ * To wake up the waiter, you must first set a global flag or something else
+ * that the wait loop tests in the "if (work to do)" part, and call
+ * SendInterrupt(INTERRUPT_GENERAL) *after* that. SendInterrupt is designed to
+ * return quickly if the interrupt is already set. In more complex scenarios
+ * with nested loops that can consume different events, you can define your
+ * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/storage/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef STORAGE_INTERRUPT_H
+#define STORAGE_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/waiteventset.h"
+
+#include <signal.h>
+
+extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
+
+/*
+ * Flags in the pending interrupts bitmask. Each value represents one bit in
+ * the bitmask.
+ */
+typedef enum
+{
+	/*
+	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+	 * waiting for an interrupt. If it's set, the backend needs to be woken up
+	 * when a bit in the pending interrupts mask is set. It's used internally
+	 * by the interrupt machinery, and cannot be used directly in the public
+	 * functions. It's named differently to distinguish it from the actual
+	 * interrupt flags.
+	 */
+	SLEEPING_ON_INTERRUPTS = 0,
+
+	/*
+	 * INTERRUPT_GENERAL is multiplexed for many reasons, like query
+	 * cancellation termination requests, recovery conflicts, and config
+	 * reload requests.  Upon receiving INTERRUPT_GENERAL, you should call
+	 * CHECK_FOR_INTERRUPTS() to process those requests.  It is also used for
+	 * various other context-dependent purposes, but note that if it's used to
+	 * wake up for other reasons, you must still call CHECK_FOR_INTERRUPTS()
+	 * once per iteration.
+	 */
+	INTERRUPT_GENERAL,
+
+	/*
+	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * it that it should continue WAL replay. It's sent by WAL receiver when
+	 * more WAL arrives, or when promotion is requested.
+	 */
+	INTERRUPT_RECOVERY_CONTINUE,
+} InterruptType;
+
+/*
+ * Test an interrupt flag.
+ */
+static inline bool
+InterruptIsPending(InterruptType reason)
+{
+	return (pg_atomic_read_u32(MyPendingInterrupts) & (1 << reason)) != 0;
+}
+
+/*
+ * Test an interrupt flag.
+ */
+static inline bool
+InterruptsPending(uint32 mask)
+{
+	return (pg_atomic_read_u32(MyPendingInterrupts) & (mask)) != 0;
+}
+
+/*
+ * Clear an interrupt flag.
+ */
+static inline void
+ClearInterrupt(InterruptType reason)
+{
+	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~(1 << reason));
+}
+
+/*
+ * Test and clear an interrupt flag.
+ */
+static inline bool
+ConsumeInterrupt(InterruptType reason)
+{
+	if (likely(!InterruptIsPending(reason)))
+		return false;
+
+	ClearInterrupt(reason);
+
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptType reason);
+extern void SendInterrupt(InterruptType reason, ProcNumber pgprocno);
+extern int	WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+#endif
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index e41dc70785a..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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-2025, 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 */
-
-/*
- * 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 114eb1f8f76..3a9221cfbc5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -17,7 +17,6 @@
 #include "access/clog.h"
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -173,9 +172,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -311,6 +307,13 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	pg_atomic_uint32 pendingInterrupts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -413,6 +416,8 @@ typedef struct PROC_HDR
 	 */
 	ProcNumber	walwriterProc;
 	ProcNumber	checkpointerProc;
+	ProcNumber	walreceiverProc;
+	ProcNumber	startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -490,9 +495,6 @@ extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
 extern void CheckDeadLockAlert(void);
 extern void LockErrorCleanup(void);
 
-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/waiteventset.h b/src/include/storage/waiteventset.h
index 9947491b619..60ececb785c 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,18 +3,17 @@
  * 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.
  *
- * 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
+ * storage/ipc/interrupt.c for detailed specifications for the exported
  * functions.
  *
- *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -25,13 +24,16 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
+#include <signal.h>
+
+#include "storage/procnumber.h"
 #include "utils/resowner.h"
 
 /*
- * 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() */
@@ -67,31 +69,35 @@ typedef struct WaitEvent
 #endif
 } WaitEvent;
 
-/* forward declarations to avoid exposing waiteventset.c implementation details */
+/* forward declaration to avoid exposing interrupt.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-typedef struct Latch Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
 
 extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  Latch *latch, void *user_data);
-extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch);
+							  uint32 interruptMask, void *user_data);
+extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, uint32 interruptMask);
+
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
+extern void InitializeInterruptWaitSet(void);
 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/port/pgsleep.c b/src/port/pgsleep.c
index dfe824fe462..404d4086057 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 set 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 e01c0c9de93..03078078893 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 2a20ffb1273..d18b2f2fa87 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -18,6 +18,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
+#include "storage/interrupt.h"
 #include "storage/shm_toc.h"
 #include "test_shm_mq.h"
 #include "utils/memutils.h"
@@ -285,11 +286,11 @@ 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);
+		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 we_bgworker_startup);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 443281addd0..ce91d74ca20 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -16,6 +16,7 @@
 #include "fmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "storage/interrupt.h"
 #include "varatt.h"
 
 #include "test_shm_mq.h"
@@ -234,13 +235,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 an 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(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 96cd304dbbc..876b5fb9ad9 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -128,7 +129,7 @@ test_shm_mq_main(Datum main_arg)
 		elog(DEBUG1, "registrant backend has exited prematurely");
 		proc_exit(1);
 	}
-	SetLatch(&registrant->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
 
 	/* 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 1e4e813314f..ba410f93a92 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -5,7 +5,7 @@
  *		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.
+ *		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
@@ -26,7 +26,7 @@
 #include "miscadmin.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt_handlers.h"
-#include "storage/latch.h"
+#include "storage/interrupt.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -213,15 +213,15 @@ 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(1 << INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 worker_spi_naptime * 1000L,
+							 worker_spi_wait_event_main);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9840060997f..93cf3ea31dd 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1271,6 +1271,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptType
 Interval
 IntervalAggState
 IntoClause
@@ -1509,7 +1510,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
-- 
2.39.5



  [text/x-patch] v6-0003-Fix-lost-wakeup-issue-in-logical-replication-laun.patch (4.1K, ../../[email protected]/4-v6-0003-Fix-lost-wakeup-issue-in-logical-replication-laun.patch)
  download | inline diff:
From a36660ae7a8de490ca0db94881fd1061ee1c63b2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:18 +0200
Subject: [PATCH v6 3/5] Fix lost wakeup issue in logical replication launcher

Fix it by using a separate interrupt bit for subscription changes.

Discussion: https://www.postgresql.org/message-id/[email protected]
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 src/backend/replication/logical/launcher.c | 23 ++++++++++++++--------
 src/include/storage/interrupt.h            |  5 ++++-
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 6578c83194b..60b28aeb2f7 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -57,7 +57,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;
@@ -805,7 +805,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;
 }
 
 /*
@@ -965,6 +965,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1110,8 +1111,12 @@ ApplyLauncherWakeupAtCommit(void)
 static 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);
 }
 
 /*
@@ -1125,8 +1130,8 @@ 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);
@@ -1158,6 +1163,7 @@ ApplyLauncherMain(Datum main_arg)
 		oldctx = MemoryContextSwitchTo(subctx);
 
 		/* Start any missing workers for enabled subscriptions. */
+		ClearInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE);
 		sublist = get_subscription_list();
 		foreach(lc, sublist)
 		{
@@ -1214,7 +1220,8 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(1 << INTERRUPT_GENERAL |
+						   1 << INTERRUPT_SUBSCRIPTION_CHANGE,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   wait_time,
 						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
@@ -1241,7 +1248,7 @@ ApplyLauncherMain(Datum main_arg)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/include/storage/interrupt.h b/src/include/storage/interrupt.h
index 779d61a8f07..8a880cd4c29 100644
--- a/src/include/storage/interrupt.h
+++ b/src/include/storage/interrupt.h
@@ -110,11 +110,14 @@ typedef enum
 	INTERRUPT_GENERAL,
 
 	/*
-	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * INTERRUPT_RECOVERY_CONTINUE is used to wake up startup process, to tell
 	 * it that it should continue WAL replay. It's sent by WAL receiver when
 	 * more WAL arrives, or when promotion is requested.
 	 */
 	INTERRUPT_RECOVERY_CONTINUE,
+
+	/* sent to logical replication launcher, when a subscription changes */
+	INTERRUPT_SUBSCRIPTION_CHANGE,
 } InterruptType;
 
 /*
-- 
2.39.5



  [text/x-patch] v6-0004-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch (23.9K, ../../[email protected]/5-v6-0004-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch)
  download | inline diff:
From c6c7b85fbe702479f8b0579b88d345162baab3e8 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:21 +0200
Subject: [PATCH v6 4/5] Use INTERRUPT_GENERAL 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_GENERAL sent directly by the postmaster when the
worker state changes.  Previously, the postmaster would send a SIGUSR1
signal, which relied on the ProcSignal system's handler setting the
interrupt flag, with the same end effect.  Now that intermediate step
is skipped.

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

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

This represents the first use of SendInterrupt() in the postmaster.
It might seem more natural to use condition variables in the
wait-for-state-change functions, so that anyone with a handle can
wait, but condition variables as currently implemented would be a lot
less robust than simple interrupts.

Author: Thomas Munro <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c            |   6 -
 doc/src/sgml/bgworker.sgml                  |  27 +++--
 src/backend/access/transam/parallel.c       |   1 -
 src/backend/postmaster/bgworker.c           | 128 +++++++++++---------
 src/backend/postmaster/postmaster.c         |   8 +-
 src/backend/replication/logical/launcher.c  |   2 -
 src/backend/storage/ipc/interrupt.c         |   4 +-
 src/backend/storage/ipc/waiteventset.c      |   5 +
 src/include/postmaster/bgworker.h           |  10 +-
 src/include/postmaster/bgworker_internals.h |   4 +-
 src/test/modules/test_shm_mq/setup.c        |   3 +-
 src/test/modules/test_shm_mq/test_shm_mq.h  |   2 +
 src/test/modules/test_shm_mq/worker.c       |   9 +-
 src/test/modules/worker_spi/worker_spi.c    |   3 -
 14 files changed, 112 insertions(+), 100 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 696cb458493..f7ce0a9a77a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -817,9 +817,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),
@@ -853,9 +850,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/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 3171054e55d..871f6869d6a 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>
@@ -108,6 +108,18 @@ 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_GENERAL</literal> when the workers state changes, which allows the
+       caller to wait for the worker to start and shut down.  That can be
+       suppressed by setting this flag.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -181,12 +193,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
@@ -258,10 +266,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/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 52b1cbc2d95..bf211efb63e 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -604,7 +604,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.
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index c17b8db545f..6cc68bf411d 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -78,6 +78,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;
 
 /*
@@ -192,8 +194,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -234,6 +237,14 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -315,20 +326,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;
+			int			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;
@@ -336,8 +347,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_GENERAL, notify_proc_number);
 
 			continue;
 		}
@@ -383,23 +394,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;
@@ -421,7 +415,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.
  */
@@ -466,8 +460,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_GENERAL, slot->notify_proc_number);
 }
 
 /*
@@ -483,12 +477,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
@@ -501,27 +495,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_GENERAL, notify_proc_number);
 }
 
 /*
- * Cancel SIGUSR1 notifications for a PID belonging to an exiting backend.
+ * Cancel notifications for a PID 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;
+		}
 	}
 }
 
@@ -553,14 +554,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;
+			int			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_GENERAL, notify_proc_number);
 		}
 	}
 }
@@ -613,11 +614,6 @@ ResetBackgroundWorkerCrashTimes(void)
 			 * resetting.
 			 */
 			rw->rw_crashed_at = 0;
-
-			/*
-			 * If there was anyone waiting for it, they're history.
-			 */
-			rw->rw_worker.bgw_notify_pid = 0;
 		}
 	}
 }
@@ -981,15 +977,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
@@ -1105,6 +1092,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.
@@ -1205,8 +1211,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)
@@ -1250,8 +1257,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)
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index a72e280845c..fcc2511da43 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4203,15 +4203,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_GENERAL, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 60b28aeb2f7..ab33444a293 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -496,7 +496,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))
@@ -939,7 +938,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);
diff --git a/src/backend/storage/ipc/interrupt.c b/src/backend/storage/ipc/interrupt.c
index 4d0edddcf99..6e58f64b096 100644
--- a/src/backend/storage/ipc/interrupt.c
+++ b/src/backend/storage/ipc/interrupt.c
@@ -105,6 +105,9 @@ RaiseInterrupt(InterruptType reason)
 
 /*
  * 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.
  */
 void
 SendInterrupt(InterruptType reason, ProcNumber pgprocno)
@@ -145,7 +148,6 @@ InitializeInterruptWaitSet(void)
 	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
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 1b7b3926864..819dce62252 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -2059,6 +2059,11 @@ WakeupMyProc(void)
 void
 WakeupOtherProc(PGPROC *proc)
 {
+	/*
+	 * 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
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 058667a47a0..ea37157b2c8 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -59,6 +59,14 @@
  */
 #define BGWORKER_BACKEND_DATABASE_CONNECTION		0x0002
 
+/*
+ * 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							0x0004
+
 /*
  * 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
@@ -97,7 +105,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 29e6f44cf08..9b4d4d5166b 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len) pg_attribute_noreturn();
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index d18b2f2fa87..5f31bf7082b 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -134,6 +134,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;
@@ -223,8 +224,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)
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 9ad9f63b44e..5b1b6cf8357 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 876b5fb9ad9..6dbe6b476ca 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -53,7 +53,6 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
 	 * Establish signal handlers.
@@ -123,13 +122,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);
-	}
-	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
+	SendInterrupt(INTERRUPT_GENERAL, 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 ba410f93a92..5736620832f 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -366,7 +366,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.
@@ -415,8 +414,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);
-- 
2.39.5



  [text/x-patch] v6-0005-Replace-ProcSignals-and-other-ad-hoc-signals-with.patch (230.4K, ../../[email protected]/6-v6-0005-Replace-ProcSignals-and-other-ad-hoc-signals-with.patch)
  download | inline diff:
From ef7c1b386aea1c41d2fd509a6962e7d8a954a7b5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 5 Mar 2025 20:35:36 +0200
Subject: [PATCH v6 5/5] Replace ProcSignals and other ad hoc signals with
 interrupts

This replaces the INTERRUPT_GENERAL, or setting the process's latch
before that, with more fine-grained interrupts. All the ProcSignals
are converted into interrupts, as well as things like timers and
config-reload requests.

Most callers of WaitInterrupt now use INTERRUPT_CFI_MASK, which is a
bitmask that includes all the interrupts that are handled by
CHECK_FOR_INTERRUPTS(). CHECK_FOR_INTERRUPTS() now clears the
interrupts bits that it processes, the caller is no longer required to
do ClearInterrupt (or ResetLatch) for those bits. If you wake up for
other interrupts that are not handled by CHECK_FOR_INTERRUPTS(), you
still need to clear those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would interrupt the process when it was not idle but was waiting on
other things; now it only wakes up when the backend is idle and is
ready to process it. That's not too significant for catchup interrupts
because they don't occur that often anyway for a few extra wakeups to
matter, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_GENERAL interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, or to wake up
processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

TODO:

 * ShutdownRequestPending became INTERRUPT_SHUTDOWN_AUX and
   ProcDiePending became INTERRUPT_DIE, but do we really need two
   different interrupts for requesting a process to terminate?

 * There are only a few interrupts bits left. Consolidate the existing
   ones, switch to a 64-bit atomic, or mulitplex some of them?

XXX: original text from Thomas's patch:

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

SIGUSR1 can now be set to SIG_IGN in most backends.  Some special cases
remain, that were not using the general sigusr1_handler (now removed).

Author: Thomas Munro <[email protected]>
Author: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c              |  25 +-
 contrib/postgres_fdw/connection.c             |   3 +-
 doc/src/sgml/sources.sgml                     |   3 +-
 src/backend/access/heap/vacuumlazy.c          |   3 +-
 src/backend/access/spgist/spgdoinsert.c       |   9 +-
 src/backend/access/transam/README.parallel    |   2 +-
 src/backend/access/transam/parallel.c         |  48 +--
 src/backend/access/transam/xlog.c             |   3 +-
 src/backend/access/transam/xlogfuncs.c        |   6 +-
 src/backend/access/transam/xlogrecovery.c     |  31 +-
 src/backend/backup/basebackup_throttle.c      |  12 +-
 src/backend/commands/async.c                  | 103 ++----
 src/backend/commands/dbcommands.c             |   1 +
 src/backend/commands/tablespace.c             |   1 +
 src/backend/commands/vacuum.c                 |   8 +-
 src/backend/executor/nodeGather.c             |   3 +-
 src/backend/libpq/be-secure.c                 |  18 +-
 src/backend/libpq/pqcomm.c                    |  18 +-
 src/backend/libpq/pqmq.c                      |  22 +-
 src/backend/postmaster/autovacuum.c           |  41 +--
 src/backend/postmaster/bgworker.c             |   7 +-
 src/backend/postmaster/bgwriter.c             |  15 +-
 src/backend/postmaster/checkpointer.c         |  69 ++--
 src/backend/postmaster/interrupt_handlers.c   |  26 +-
 src/backend/postmaster/pgarch.c               |  20 +-
 src/backend/postmaster/postmaster.c           |   3 +-
 src/backend/postmaster/startup.c              |  16 +-
 src/backend/postmaster/syslogger.c            |   8 +-
 src/backend/postmaster/walsummarizer.c        |  26 +-
 src/backend/postmaster/walwriter.c            |   8 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |  12 +-
 .../replication/logical/applyparallelworker.c |  57 +---
 src/backend/replication/logical/launcher.c    |  43 +--
 src/backend/replication/logical/slotsync.c    |  57 ++--
 src/backend/replication/logical/tablesync.c   |  12 +-
 src/backend/replication/logical/worker.c      |  19 +-
 src/backend/replication/slot.c                |  89 ++---
 src/backend/replication/slotfuncs.c           |  18 +-
 src/backend/replication/syncrep.c             |  13 +-
 src/backend/replication/walreceiver.c         |  19 +-
 src/backend/replication/walsender.c           |  78 +++--
 src/backend/storage/buffer/bufmgr.c           |  18 +-
 src/backend/storage/ipc/interrupt.c           |  15 +-
 src/backend/storage/ipc/ipc.c                 |   6 +-
 src/backend/storage/ipc/procarray.c           |  20 +-
 src/backend/storage/ipc/procsignal.c          | 242 ++------------
 src/backend/storage/ipc/shm_mq.c              |  64 ++--
 src/backend/storage/ipc/signalfuncs.c         |   2 +-
 src/backend/storage/ipc/sinval.c              |  52 +--
 src/backend/storage/ipc/sinvaladt.c           |  14 +-
 src/backend/storage/ipc/standby.c             |  81 +++--
 src/backend/storage/ipc/waiteventset.c        |   4 +-
 src/backend/storage/lmgr/condition_variable.c |   3 +-
 src/backend/storage/lmgr/predicate.c          |   5 +-
 src/backend/storage/lmgr/proc.c               |  15 +-
 src/backend/tcop/postgres.c                   | 313 ++++++++----------
 src/backend/utils/activity/pgstat_database.c  |  20 +-
 src/backend/utils/adt/mcxtfuncs.c             |  10 +-
 src/backend/utils/adt/misc.c                  |   3 +-
 src/backend/utils/init/globals.c              |  13 +-
 src/backend/utils/init/postinit.c             |  20 +-
 src/backend/utils/misc/timeout.c              |   6 -
 src/backend/utils/mmgr/mcxt.c                 |  20 +-
 src/include/access/parallel.h                 |   4 -
 src/include/commands/async.h                  |   6 -
 src/include/libpq/libpq-be-fe-helpers.h       |  14 +-
 src/include/libpq/pqmq.h                      |   2 +-
 src/include/miscadmin.h                       |  61 ++--
 src/include/pgstat.h                          |   2 +-
 src/include/postmaster/interrupt_handlers.h   |   5 -
 src/include/replication/logicalworker.h       |   5 -
 src/include/replication/slot.h                |   6 +-
 src/include/replication/walsender.h           |   2 +-
 src/include/replication/walsender_private.h   |   1 +
 src/include/replication/worker_internal.h     |   5 +-
 src/include/storage/interrupt.h               | 270 ++++++++++++---
 src/include/storage/procarray.h               |   7 +-
 src/include/storage/procsignal.h              |  41 ---
 src/include/storage/sinval.h                  |   5 -
 src/include/storage/standby.h                 |   4 +-
 src/include/storage/waiteventset.h            |   5 +-
 src/include/tcop/tcopprot.h                   |   6 +-
 src/include/utils/memutils.h                  |   1 -
 src/test/modules/test_shm_mq/setup.c          |   9 +-
 src/test/modules/test_shm_mq/test.c           |   6 +-
 src/test/modules/worker_spi/worker_spi.c      |   9 +-
 86 files changed, 1040 insertions(+), 1357 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index f7ce0a9a77a..ff6bd3252e8 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -40,7 +40,6 @@
 #include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -150,7 +149,7 @@ autoprewarm_main(Datum main_arg)
 	/* Establish signal handlers; once that's done, unblock signals. */
 	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	BackgroundWorkerUnblockSignals();
 
 	/* Create (if necessary) and attach to our shared memory area. */
@@ -199,24 +198,22 @@ autoprewarm_main(Datum main_arg)
 	if (first_time)
 	{
 		apw_load_buffers();
-		final_dump_allowed = !ShutdownRequestPending;
+		final_dump_allowed = !InterruptPending(INTERRUPT_SHUTDOWN_AUX);
 		last_dump_time = GetCurrentTimestamp();
 	}
 
 	/* Periodically dump buffers until terminated. */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 	{
 		/* In case of a SIGHUP, just reload the configuration. */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (autoprewarm_interval <= 0)
 		{
 			/* We're only dumping at shutdown, so just wait forever. */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX |
+								 INTERRUPT_CONFIG_RELOAD,
 								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
 								 -1L,
 								 PG_WAIT_EXTENSION);
@@ -243,14 +240,12 @@ autoprewarm_main(Datum main_arg)
 			}
 
 			/* Sleep until the next dump time. */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX |
+								 INTERRUPT_CONFIG_RELOAD,
 								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								 delay_in_ms,
 								 PG_WAIT_EXTENSION);
 		}
-
-		/* Reset the latch, loop. */
-		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	/*
@@ -395,7 +390,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_SHUTDOWN_AUX))
 			break;
 
 		/*
@@ -416,7 +411,7 @@ apw_load_buffers(void)
 	LWLockRelease(&apw_state->lock);
 
 	/* Report our success, if we were able to finish. */
-	if (!ShutdownRequestPending)
+	if (!InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 		ereport(LOG,
 				(errmsg("autoprewarm successfully prewarmed %d of %d previously-loaded blocks",
 						apw_state->prewarmed_blocks, num_elements)));
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 4c4f9ea84af..45f887722de 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1654,12 +1654,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 					pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 				/* Sleep until there's something to do */
-				wc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+				wc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK,
 										   WL_INTERRUPT | WL_SOCKET_READABLE |
 										   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 										   PQsocket(conn),
 										   cur_timeout, pgfdw_we_cleanup_result);
-				ClearInterrupt(INTERRUPT_GENERAL);
 
 				CHECK_FOR_INTERRUPTS();
 
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index fc642f5f713..018d6970c24 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -1000,8 +1000,7 @@ MemoryContextSwitchTo(MemoryContext context)
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    RaiseInterrupt(INTERRUPT_GENERAL);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index f8df18660ed..9dc09979798 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -3243,11 +3243,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK,
 								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
 								 WAIT_EVENT_VACUUM_TRUNCATE);
-			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index af6b27b2135..98ed431b470 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2040,8 +2040,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * XXX: INTERRUPT_CFI_MASK covers more than just query cancel and die.
+		 * Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(INTERRUPT_CFI_MASK))
 		{
 			result = false;
 			break;
@@ -2167,7 +2170,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(INTERRUPT_CFI_MASK))
 			{
 				result = false;
 				break;
@@ -2343,7 +2346,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_CFI_MASK));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index bf211efb63e..68e6c22512b 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -115,9 +115,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -127,9 +124,6 @@ static FixedParallelState *MyFixedParallelState;
 /* List of active parallel contexts. */
 static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list);
 
-/* Backend-local copy of data from FixedParallelState. */
-static pid_t ParallelLeaderPid;
-
 /*
  * List of internal parallel worker entry points.  We need this for
  * reasons explained in LookupParallelWorkerFunction(), below.
@@ -243,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_CFI_MASK))
 		pcxt->nworkers = 0;
 
 	/*
@@ -759,16 +753,20 @@ WaitForParallelWorkersToAttach(ParallelContext *pcxt)
 			{
 				/*
 				 * Worker not yet started, so we must wait.  The postmaster
-				 * will notify us if the worker's state changes.  The
-				 * interrupt might also get set for some other reason, but if
-				 * so we'll just end up waiting for the same worker again.
+				 * will notify us with INTERRUPT_GENERAL if the worker's state
+				 * changes.  The interrupt might also get set for some other
+				 * reason, but if so we'll just end up waiting for the same
+				 * worker again.
 				 */
-				rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+				rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 								   WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
 								   -1, WAIT_EVENT_BGWORKER_STARTUP);
 
 				if (rc & WL_INTERRUPT)
+				{
+					CHECK_FOR_INTERRUPTS();
 					ClearInterrupt(INTERRUPT_GENERAL);
+				}
 			}
 		}
 
@@ -883,7 +881,7 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 			}
 		}
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
 							 WAIT_EVENT_PARALLEL_FINISH);
 		ClearInterrupt(INTERRUPT_GENERAL);
@@ -1027,21 +1025,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1077,7 +1060,7 @@ ProcessParallelMessages(void)
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
 	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
+	ClearInterrupt(INTERRUPT_PARALLEL_MESSAGE);
 
 	dlist_foreach(iter, &pcxt_list)
 	{
@@ -1358,7 +1341,6 @@ ParallelWorkerMain(Datum main_arg)
 	MyFixedParallelState = fps;
 
 	/* Arrange to signal the leader if we exit. */
-	ParallelLeaderPid = fps->parallel_leader_pid;
 	ParallelLeaderProcNumber = fps->parallel_leader_proc_number;
 	before_shmem_exit(ParallelWorkerShutdown, PointerGetDatum(seg));
 
@@ -1374,8 +1356,7 @@ ParallelWorkerMain(Datum main_arg)
 	shm_mq_set_sender(mq, MyProc);
 	mqh = shm_mq_attach(mq, seg, NULL);
 	pq_redirect_to_shm_mq(seg, mqh);
-	pq_set_parallel_leader(fps->parallel_leader_pid,
-						   fps->parallel_leader_proc_number);
+	pq_set_parallel_leader(fps->parallel_leader_proc_number);
 
 	/*
 	 * Hooray! Primary initialization is complete.  Now, we need to set up our
@@ -1614,9 +1595,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 4740dc89ef8..4f218eec41d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -9382,11 +9382,10 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK,
 								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								 1000L,
 								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ClearInterrupt(INTERRUPT_GENERAL);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index ffe0237326b..d6defe26b95 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -708,6 +708,8 @@ pg_promote(PG_FUNCTION_ARGS)
 				 errmsg("failed to send signal to postmaster: %m")));
 	}
 
+	/* XXX: we could interrupt the startup process directly */
+
 	/* return immediately if waiting was not requested */
 	if (!wait)
 		PG_RETURN_BOOL(true);
@@ -718,14 +720,12 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
 						   1000L / WAITS_PER_SECOND,
 						   WAIT_EVENT_PROMOTE);
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 1e31b0b761a..f42559861e7 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -3004,18 +3004,12 @@ recoveryApplyDelay(XLogReaderState *record)
 
 	while (true)
 	{
-		/*
-		 * INTERRUPT_GENERAL is used for all the usual interrupts, like config
-		 * reloads.  The wakeups when more WAL arrive use a different
-		 * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL
-		 * arriving don't wake up the startup process excessively when we're
-		 * waiting in other places, like for recovery conflicts.
-		 */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		/* This might change recovery_min_apply_delay. */
 		ProcessStartupProcInterrupts();
 
+		/*
+		 * INTERRUPT_RECOVERY_CONTINUE indicates that more WAL has arrived.
+		 */
 		ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 		if (CheckForStandbyTrigger())
 			break;
@@ -3037,8 +3031,8 @@ recoveryApplyDelay(XLogReaderState *record)
 
 		elog(DEBUG2, "recovery apply delay %ld milliseconds", msecs);
 
-		(void) WaitInterrupt(1 << INTERRUPT_RECOVERY_CONTINUE |
-							 1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK |
+							 INTERRUPT_RECOVERY_CONTINUE,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 msecs,
 							 WAIT_EVENT_RECOVERY_APPLY_DELAY);
@@ -3696,18 +3690,18 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 						/* Do background tasks that might benefit us later. */
 						KnownAssignedTransactionIdsIdleMaintenance();
 
-						(void) WaitInterrupt(1 << INTERRUPT_RECOVERY_CONTINUE |
-											 1 << INTERRUPT_GENERAL,
+						(void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK |
+											 INTERRUPT_RECOVERY_CONTINUE,
 											 WL_INTERRUPT | WL_TIMEOUT |
 											 WL_EXIT_ON_PM_DEATH,
 											 wait_time,
 											 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
-						ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 						now = GetCurrentTimestamp();
 
-						/* Handle interrupt signals of startup process */
-						ClearInterrupt(INTERRUPT_GENERAL);
+						/* Handle standard interrupts of startup process */
 						ProcessStartupProcInterrupts();
+
+						ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 					}
 					last_fail_time = now;
 					currentSource = XLOG_FROM_ARCHIVE;
@@ -3973,8 +3967,8 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 					 * Wait for more WAL to arrive, when we will be woken
 					 * immediately by the WAL receiver.
 					 */
-					(void) WaitInterrupt(1 << INTERRUPT_RECOVERY_CONTINUE |
-										 1 << INTERRUPT_GENERAL,
+					(void) WaitInterrupt(INTERRUPT_STARTUP_PROC_MASK |
+										 INTERRUPT_RECOVERY_CONTINUE,
 										 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
 										 -1L,
 										 WAIT_EVENT_RECOVERY_WAL_STREAM);
@@ -3998,7 +3992,6 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 		 * This possibly-long loop needs to handle interrupts of startup
 		 * process.
 		 */
-		ClearInterrupt(INTERRUPT_GENERAL);
 		ProcessStartupProcInterrupts();
 	}
 
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index 05443e0853a..50edc67534a 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -155,6 +155,8 @@ throttle(bbsink_throttle *sink, size_t increment)
 					sleep;
 		int			wait_result;
 
+		CHECK_FOR_INTERRUPTS();
+
 		/* Time elapsed since the last measurement (and possible wake up). */
 		elapsed = GetCurrentTimestamp() - sink->throttled_last;
 
@@ -163,23 +165,15 @@ throttle(bbsink_throttle *sink, size_t increment)
 		if (sleep <= 0)
 			break;
 
-		ClearInterrupt(INTERRUPT_GENERAL);
-
-		/* We're eating a wakeup, so check for interrupts */
-		CHECK_FOR_INTERRUPTS();
-
 		/*
 		 * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be
 		 * the maximum time to sleep. Thus the cast to long is safe.
 		 */
-		wait_result = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		wait_result = WaitInterrupt(INTERRUPT_CFI_MASK,
 									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 									(long) (sleep / 1000),
 									WAIT_EVENT_BASE_BACKUP_THROTTLE);
 
-		if (wait_result & WL_INTERRUPT)
-			CHECK_FOR_INTERRUPTS();
-
 		/* Done waiting? */
 		if (wait_result & WL_TIMEOUT)
 			break;
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 201d7150083..989c50e1ee0 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -39,7 +39,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -72,7 +72,7 @@
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
  *	  SignalBackends(), which scans the list of listening backends and sends a
- *	  PROCSIG_NOTIFY_INTERRUPT signal to every listening backend (we don't
+ *	  INTERRUPT_ASYNC_NOTIFY interrupt to every listening backend (we don't
  *	  know which backend is listening on which channel so we must signal them
  *	  all).  We can exclude backends that are already up to date, though, and
  *	  we can also exclude backends that are in other databases (unless they
@@ -90,13 +90,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  raises INTERRUPT_GENERAL, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  ProcessClientReadInterrupt()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -127,7 +125,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -143,7 +140,6 @@
 #include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/guc_hooks.h"
@@ -271,7 +267,7 @@ typedef struct QueueBackendStatus
  * NotifyQueueTailLock, then NotifyQueueLock, and lastly SLRU bank lock.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -404,15 +400,6 @@ struct NotificationHash
 
 static NotificationList *pendingNotifies = NULL;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and raises the INTERRUPT_GENERAL interrupt.
- * ProcessNotifyInterrupt() will then be called whenever it's safe to actually
- * deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -1581,29 +1568,25 @@ asyncQueueFillWarning(void)
 static void
 SignalBackends(void)
 {
-	int32	   *pids;
 	ProcNumber *procnos;
 	int			count;
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this loop just builds a
-	 * list of target PIDs.
+	 * list of target backends.
 	 *
 	 * XXX in principle these pallocs could fail, which would be bad. Maybe
 	 * preallocate the arrays?  They're not that large, though.
 	 */
-	pids = (int32 *) palloc(MaxBackends * sizeof(int32));
 	procnos = (ProcNumber *) palloc(MaxBackends * sizeof(ProcNumber));
 	count = 0;
 
 	LWLockAcquire(NotifyQueueLock, LW_EXCLUSIVE);
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid = QUEUE_BACKEND_PID(i);
 		QueuePosition pos;
 
-		Assert(pid != InvalidPid);
 		pos = QUEUE_BACKEND_POS(i);
 		if (QUEUE_BACKEND_DBOID(i) == MyDatabaseId)
 		{
@@ -1625,38 +1608,27 @@ SignalBackends(void)
 				continue;
 		}
 		/* OK, need to signal this one */
-		pids[count] = pid;
 		procnos[count] = i;
 		count++;
 	}
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = pids[i];
+		ProcNumber procno = procnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, procnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 
-	pfree(pids);
 	pfree(procnos);
 }
 
@@ -1793,39 +1765,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	RaiseInterrupt(INTERRUPT_GENERAL);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted with
- *		INTERRUPT_GENERAL, and this routine will get called.  If we are truly
- *		idle (ie, *not* inside a transaction block), process the incoming
- *		notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if a notify signal occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -1834,11 +1779,10 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
 	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 		ProcessIncomingNotify(flush);
 }
 
@@ -2183,9 +2127,6 @@ asyncQueueAdvanceTail(void)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (listenChannels == NIL)
 		return;
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 1753b289074..d1557542b76 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -60,6 +60,7 @@
 #include "storage/lmgr.h"
 #include "storage/md.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index a9005cc7212..80b6c721bac 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -70,6 +70,7 @@
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
+#include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 8017448de56..4d6b4d0b7d1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -48,6 +48,7 @@
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/interrupt_handlers.h"
 #include "storage/bufmgr.h"
+#include "storage/interrupt.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -2397,8 +2398,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2407,9 +2407,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();
 	}
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 5d0a58384b0..bf957ab1a87 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -383,7 +383,8 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK |
+								 INTERRUPT_GENERAL,
 								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 								 WAIT_EVENT_EXECUTE_GATHER);
 			ClearInterrupt(INTERRUPT_GENERAL);
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index c61f0bdb4c0..3d05e80364e 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -179,10 +179,11 @@ ssize_t
 secure_read(Port *port, void *ptr, size_t len)
 {
 	ssize_t		n;
+	uint32		interruptMask;
 	int			waitfor;
 
 	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
+	interruptMask = ProcessClientReadInterrupt(false);
 
 retry:
 #ifdef USE_SSL
@@ -214,6 +215,7 @@ retry:
 		Assert(waitfor);
 
 		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -243,8 +245,7 @@ retry:
 		/* Handle interrupt. */
 		if (event.events & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
-			ProcessClientReadInterrupt(true);
+			interruptMask = ProcessClientReadInterrupt(true);
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -259,7 +260,7 @@ retry:
 	 * Process interrupts that happened during a successful (or non-blocking,
 	 * or hard-failed) read.
 	 */
-	ProcessClientReadInterrupt(false);
+	(void) ProcessClientReadInterrupt(false);
 
 	return n;
 }
@@ -306,10 +307,11 @@ ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
 {
 	ssize_t		n;
+	uint32		interruptMask;
 	int			waitfor;
 
 	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
+	interruptMask = ProcessClientWriteInterrupt(false);
 
 retry:
 	waitfor = 0;
@@ -340,6 +342,7 @@ retry:
 		Assert(waitfor);
 
 		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -353,8 +356,7 @@ retry:
 		/* Handle interrupt. */
 		if (event.events & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
-			ProcessClientWriteInterrupt(true);
+			interruptMask = ProcessClientWriteInterrupt(true);
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -369,7 +371,7 @@ retry:
 	 * Process interrupts that happened during a successful (or non-blocking,
 	 * or hard-failed) write.
 	 */
-	ProcessClientWriteInterrupt(false);
+	(void) ProcessClientWriteInterrupt(false);
 
 	return n;
 }
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index daf7c7435bb..5a92803070e 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -309,7 +309,7 @@ pq_init(ClientSocket *client_sock)
 	socket_pos = AddWaitEventToSet(FeBeWaitSet, WL_SOCKET_WRITEABLE,
 								   port->sock, 0, NULL);
 	interrupt_pos = AddWaitEventToSet(FeBeWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
-									  1 << INTERRUPT_GENERAL, NULL);
+									  INTERRUPT_CFI_MASK, NULL);
 	AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
 					  0, NULL);
 
@@ -1413,8 +1413,7 @@ internal_flush_buffer(const char *buf, size_t *start, size_t *end)
 			 * the connection.
 			 */
 			*start = *end = 0;
-			ClientConnectionLost = 1;
-			InterruptPending = 1;
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
 			return EOF;
 		}
 
@@ -2065,24 +2064,13 @@ pq_check_connection(void)
 	 * all FeBeWaitSet socket wait sites do the same.
 	 */
 	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, WL_SOCKET_CLOSED, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, 0);
 
-retry:
 	rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0);
 	for (int i = 0; i < rc; ++i)
 	{
 		if (events[i].events & WL_SOCKET_CLOSED)
 			return false;
-		if (events[i].events & WL_INTERRUPT)
-		{
-			/*
-			 * An interrupt event might be preventing other events from being
-			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect INTERRUPT_GENERAL to survive
-			 * across CHECK_FOR_INTERRUPTS().
-			 */
-			ClearInterrupt(INTERRUPT_GENERAL);
-			goto retry;
-		}
 	}
 
 	return true;
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index ef080f63112..6394f924399 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -26,7 +26,6 @@
 
 static shm_mq_handle *pq_mq_handle;
 static bool pq_mq_busy = false;
-static pid_t pq_mq_parallel_leader_pid = 0;
 static ProcNumber pq_mq_parallel_leader_proc_number = INVALID_PROC_NUMBER;
 
 static void pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg);
@@ -72,14 +71,13 @@ pq_cleanup_redirect_to_shm_mq(dsm_segment *seg, Datum arg)
 }
 
 /*
- * Arrange to SendProcSignal() to the parallel leader each time we transmit
- * message data via the shm_mq.
+ * Arrange to interrupt to the parallel leader each time we transmit message
+ * data via the shm_mq.
  */
 void
-pq_set_parallel_leader(pid_t pid, ProcNumber procNumber)
+pq_set_parallel_leader(ProcNumber procNumber)
 {
 	Assert(PqCommMethods == &PqCommMqMethods);
-	pq_mq_parallel_leader_pid = pid;
 	pq_mq_parallel_leader_proc_number = procNumber;
 }
 
@@ -164,25 +162,23 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		 */
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
 			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
+				SendInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE,
+							  pq_mq_parallel_leader_proc_number);
 			else
 			{
 				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
+				SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+							  pq_mq_parallel_leader_proc_number);
 			}
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
 		ClearInterrupt(INTERRUPT_GENERAL);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e27da9fd88a..4bbf413e3ec 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -401,7 +401,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
 	pqsignal(SIGCHLD, SIG_DFL);
@@ -451,7 +451,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 
 		/* Forget any pending QueryCancel or timeout request */
 		disable_all_timeouts(false);
-		QueryCancelPending = false; /* second to avoid race condition */
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* second to avoid race condition */
 
 		/* Report the error to the server log */
 		EmitErrorReport();
@@ -492,7 +492,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_SHUTDOWN_AUX))
 			AutoVacLauncherShutdown();
 
 		/*
@@ -551,7 +551,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (!AutoVacuumingActive())
 	{
-		if (!ShutdownRequestPending)
+		if (!InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 			do_start_worker();
 		proc_exit(0);			/* done */
 	}
@@ -568,7 +568,7 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
@@ -587,15 +587,19 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		 * Wait until naptime expires or we get some type of signal (all the
 		 * signal handlers will wake us by calling RaiseInterrupt).
 		 */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX |
+							 INTERRUPT_CONFIG_RELOAD |
+							 INTERRUPT_BARRIER |
+							 INTERRUPT_LOG_MEMORY_CONTEXT |
+							 INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
 							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		ProcessAutoVacLauncherInterrupts();
 
+		ClearInterrupt(INTERRUPT_GENERAL);
+
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
@@ -745,14 +749,13 @@ static void
 ProcessAutoVacLauncherInterrupts(void)
 {
 	/* the normal shutdown case */
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 		AutoVacLauncherShutdown();
 
-	if (ConfigReloadPending)
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 	{
 		int			autovacuum_max_workers_prev = autovacuum_max_workers;
 
-		ConfigReloadPending = false;
 		ProcessConfigFile(PGC_SIGHUP);
 
 		/* shutdown requested in config file? */
@@ -772,11 +775,11 @@ ProcessAutoVacLauncherInterrupts(void)
 	}
 
 	/* Process barrier events */
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 
 	/* Process sinval catchup interrupts that happened while sleeping */
@@ -1408,7 +1411,7 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
 	pqsignal(SIGCHLD, SIG_DFL);
@@ -2278,9 +2281,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);
 
 			/*
@@ -2438,7 +2440,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();
 		{
@@ -2522,9 +2524,8 @@ deleted:
 		 * Check for config changes before acquiring lock for further jobs.
 		 */
 		CHECK_FOR_INTERRUPTS();
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			VacuumUpdateCosts();
 		}
@@ -2636,7 +2637,7 @@ perform_work_item(AutoVacuumWorkItem *workitem)
 		 * (we're done with it, so it would make no sense to cancel at this
 		 * point.)
 		 */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	}
 	PG_CATCH();
 	{
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 6cc68bf411d..08969ff7693 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -26,7 +26,6 @@
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -751,7 +750,7 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 		 * SIGINT is used to signal canceling the current action
 		 */
 		pqsignal(SIGINT, StatementCancelHandler);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 
 		/* XXX Any other handlers needed here? */
@@ -1233,7 +1232,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 		if (status != BGWH_NOT_YET_STARTED)
 			break;
 
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
 						   WAIT_EVENT_BGWORKER_STARTUP);
 
@@ -1277,7 +1276,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 		if (status == BGWH_STOPPED)
 			break;
 
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
 						   WAIT_EVENT_BGWORKER_SHUTDOWN);
 
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index a37257218b1..8d8b85652be 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -45,7 +45,6 @@
 #include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "storage/smgr.h"
 #include "storage/standby.h"
 #include "utils/memutils.h"
@@ -106,7 +105,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
 
 	/*
@@ -224,9 +223,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		bool		can_hibernate;
 		int			rc;
 
-		/* Clear any already-pending wakeups */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
+		/* Process any pending standard interrupts */
 		ProcessMainLoopInterrupts();
 
 		/*
@@ -303,7 +300,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		 * down with interrupt events that are likely to happen frequently
 		 * during normal operation.
 		 */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);
 
@@ -327,10 +324,14 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		 */
 		if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate)
 		{
+			/* Clear any already-pending wakeups */
+			ClearInterrupt(INTERRUPT_GENERAL);
+
 			/* Ask for notification at next buffer allocation */
 			StrategyNotifyBgWriter(MyProcNumber);
 			/* Sleep ... */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+			(void) WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK |
+								 INTERRUPT_GENERAL,
 								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								 BgWriterDelay * HIBERNATE_FACTOR,
 								 WAIT_EVENT_BGWRITER_HIBERNATE);
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index da6caa64457..8ce5f6b7f00 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -81,10 +81,11 @@
  * The algorithm for backends is:
  *	1. Record current values of ckpt_failed and ckpt_started, and
  *	   set request flags, while holding ckpt_lck.
- *	2. Send signal to request checkpoint.
+ *	2. Send INTERRUPT_GENERAL to wake up the checkpointer.
  *	3. Sleep until ckpt_started changes.  Now you know a checkpoint has
  *	   begun since you started this algorithm (although *not* that it was
- *	   specifically initiated by your signal), and that it is using your flags.
+ *	   specifically initiated by your interrupt), and that it is using your
+ *	   flags.
  *	4. Record new value of ckpt_started.
  *	5. Sleep until ckpt_done >= saved value of ckpt_started.  (Use modulo
  *	   arithmetic here in case counters wrap around.)  Now you know a
@@ -146,7 +147,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;
@@ -202,7 +202,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
 	/*
@@ -356,12 +356,12 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
-		 * Process any requests or signals received recently.
+		 * Process any requests or interrupts received recently.
 		 */
 		AbsorbSyncRequests();
 
 		ProcessCheckpointerInterrupts();
-		if (ShutdownXLOGPending || ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG) || InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 			break;
 
 		/*
@@ -538,7 +538,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 			 * interrupt might have been reset (e.g. in CheckpointWriteDelay).
 			 */
 			ProcessCheckpointerInterrupts();
-			if (ShutdownXLOGPending || ShutdownRequestPending)
+			if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG) || InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 				break;
 		}
 
@@ -557,7 +557,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);
@@ -573,10 +573,18 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 			cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
 		}
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
-							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 cur_timeout * 1000L /* convert to ms */ ,
-							 WAIT_EVENT_CHECKPOINTER_MAIN);
+		(void) WaitInterrupt(
+			/* these are handled in the main loop */
+			INTERRUPT_GENERAL | 	/* checkpoint requested */
+			INTERRUPT_SHUTDOWN_XLOG |
+			INTERRUPT_SHUTDOWN_AUX |
+			/* ProcessCheckpointerInterrupts() */
+			INTERRUPT_BARRIER |
+			INTERRUPT_LOG_MEMORY_CONTEXT |
+			INTERRUPT_CONFIG_RELOAD,
+			WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+			cur_timeout * 1000L /* convert to ms */ ,
+			WAIT_EVENT_CHECKPOINTER_MAIN);
 	}
 
 	/*
@@ -585,7 +593,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -603,7 +611,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);
 	}
 
 	/*
@@ -613,15 +621,16 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		ProcessCheckpointerInterrupts();
 
-		if (ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 			break;
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_SHUTDOWN_AUX |
+							 /* ProcessCheckpointerInterrupts() */
+							 INTERRUPT_BARRIER |
+							 INTERRUPT_LOG_MEMORY_CONTEXT |
+							 INTERRUPT_CONFIG_RELOAD,
 							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
 							 0,
 							 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
@@ -637,12 +646,11 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 static void
 ProcessCheckpointerInterrupts(void)
 {
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
-	if (ConfigReloadPending)
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 	{
-		ConfigReloadPending = false;
 		ProcessConfigFile(PGC_SIGHUP);
 
 		/*
@@ -660,7 +668,7 @@ ProcessCheckpointerInterrupts(void)
 	}
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 }
 
@@ -778,14 +786,13 @@ CheckpointWriteDelay(int flags, double progress)
 	 * in which case we just try to catch up as quickly as possible.
 	 */
 	if (!(flags & CHECKPOINT_IMMEDIATE) &&
-		!ShutdownXLOGPending &&
-		!ShutdownRequestPending &&
+		!InterruptPending(INTERRUPT_SHUTDOWN_XLOG) &&
+		!InterruptPending(INTERRUPT_SHUTDOWN_AUX) &&
 		!ImmediateCheckpointRequested() &&
 		IsCheckpointOnSchedule(progress))
 	{
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			/* update shmem copies of config variables */
 			UpdateSharedMemoryConfig();
@@ -805,7 +812,8 @@ CheckpointWriteDelay(int flags, double progress)
 		 * Checkpointer and bgwriter are no longer related so take the Big
 		 * Sleep.
 		 */
-		WaitInterrupt(1 << INTERRUPT_GENERAL,
+		WaitInterrupt(INTERRUPT_GENERAL |
+					  INTERRUPT_CONFIG_RELOAD,
 					  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
 					  100,
 					  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
@@ -823,7 +831,7 @@ CheckpointWriteDelay(int flags, double progress)
 	}
 
 	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 }
 
@@ -917,8 +925,7 @@ IsCheckpointOnSchedule(double progress)
 static void
 ReqShutdownXLOG(SIGNAL_ARGS)
 {
-	ShutdownXLOGPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_SHUTDOWN_XLOG);
 }
 
 
diff --git a/src/backend/postmaster/interrupt_handlers.c b/src/backend/postmaster/interrupt_handlers.c
index 10a4710f403..f08b9684e0b 100644
--- a/src/backend/postmaster/interrupt_handlers.c
+++ b/src/backend/postmaster/interrupt_handlers.c
@@ -24,29 +24,23 @@
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
 /*
  * Simple interrupt handler for main loops of background processes.
  */
 void
 ProcessMainLoopInterrupts(void)
 {
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		ProcessConfigFile(PGC_SIGHUP);
-	}
 
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 		proc_exit(0);
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 }
 
@@ -54,14 +48,13 @@ ProcessMainLoopInterrupts(void)
  * Simple signal handler for triggering a configuration reload.
  *
  * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt at
+ * convenient places inside main loops, or else call HandleMainLoopInterrupts.
  */
 void
 SignalHandlerForConfigReload(SIGNAL_ARGS)
 {
-	ConfigReloadPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 
 /*
@@ -98,12 +91,11 @@ SignalHandlerForCrashExit(SIGNAL_ARGS)
  * writer and the logical replication parallel apply worker exits on either
  * SIGINT or SIGTERM.
  *
- * ShutdownRequestPending should be checked at a convenient place within the
+ * INTERRUPT_SHUTDOWN_AUX should be checked at a convenient place within the
  * main loop, or else the main loop should call ProcessMainLoopInterrupts.
  */
 void
 SignalHandlerForShutdownRequest(SIGNAL_ARGS)
 {
-	ShutdownRequestPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_SHUTDOWN_AUX);
 }
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 1e921c0e651..7dde75cd709 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -231,7 +231,7 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
 	/* Reset some signals that are accepted by postmaster but not here */
@@ -332,7 +332,7 @@ pgarch_MainLoop(void)
 		 * idea.  If more than 60 seconds pass since SIGTERM, exit anyway, so
 		 * that the postmaster can start a new archiver if needed.
 		 */
-		if (ShutdownRequestPending)
+		if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 		{
 			time_t		curtime = time(NULL);
 
@@ -354,7 +354,11 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+			rc = WaitInterrupt(INTERRUPT_GENERAL |
+							   INTERRUPT_SHUTDOWN_AUX |
+							   INTERRUPT_CONFIG_RELOAD |
+							   INTERRUPT_BARRIER |
+							   INTERRUPT_LOG_MEMORY_CONTEXT,
 							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
 							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
 							   WAIT_EVENT_ARCHIVER_MAIN);
@@ -406,7 +410,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_SHUTDOWN_AUX) || !PostmasterIsAlive())
 				return;
 
 			/*
@@ -857,19 +861,19 @@ pgarch_die(int code, Datum arg)
 static void
 ProcessPgArchInterrupts(void)
 {
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 
-	if (ConfigReloadPending)
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 	{
 		char	   *archiveLib = pstrdup(XLogArchiveLibrary);
 		bool		archiveLibChanged;
 
-		ConfigReloadPending = false;
+		ClearInterrupt(INTERRUPT_CONFIG_RELOAD);
 		ProcessConfigFile(PGC_SIGHUP);
 
 		if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index fcc2511da43..9524c26a140 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -1621,7 +1621,8 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
 	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
-					  1 << INTERRUPT_GENERAL, NULL);
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_GENERAL,
+					  NULL);
 
 	if (accept_connections)
 	{
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index f2ac862513b..1ebd10d684d 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -26,6 +26,7 @@
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -50,7 +51,6 @@
 /*
  * Flags set by interrupt handlers for later service in the redo loop.
  */
-static volatile sig_atomic_t got_SIGHUP = false;
 static volatile sig_atomic_t shutdown_requested = false;
 static volatile sig_atomic_t promote_signaled = false;
 
@@ -101,8 +101,7 @@ StartupProcTriggerHandler(SIGNAL_ARGS)
 static void
 StartupProcSigHupHandler(SIGNAL_ARGS)
 {
-	got_SIGHUP = true;
-	WakeupRecovery();
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -161,11 +160,8 @@ ProcessStartupProcInterrupts(void)
 	/*
 	 * Process any requests or signals received recently.
 	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		StartupRereadConfig();
-	}
 
 	/*
 	 * Check if we were requested to exit without finishing recovery.
@@ -187,11 +183,11 @@ ProcessStartupProcInterrupts(void)
 		exit(1);
 
 	/* Process barrier events */
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 }
 
@@ -241,7 +237,7 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
 	/*
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 803dcde071b..4d9edb36cb3 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -338,7 +338,10 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * (including the postmaster).
 	 */
 	wes = CreateWaitEventSet(NULL, 2);
-	AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET, 1 << INTERRUPT_GENERAL, NULL);
+	AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD |
+					  INTERRUPT_GENERAL, /* for log rotation */
+					  NULL);
 #ifndef WIN32
 	AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], 0, NULL);
 #endif
@@ -361,9 +364,8 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 		/*
 		 * Process any requests or signals received recently.
 		 */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 
 			/*
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 9288a1b299a..e3bfb613609 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -251,7 +251,7 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
 	/* Advertise ourselves. */
@@ -316,7 +316,12 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 		 * So a really fast retry time doesn't seem to be especially
 		 * beneficial, and it will clutter the logs.
 		 */
-		(void) WaitInterrupt(0, WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10000,
+		(void) WaitInterrupt(INTERRUPT_GENERAL |
+							 INTERRUPT_BARRIER |
+							 INTERRUPT_CONFIG_RELOAD |
+							 INTERRUPT_SHUTDOWN_AUX |
+							 INTERRUPT_LOG_MEMORY_CONTEXT,
+							 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH, 10000,
 							 WAIT_EVENT_WAL_SUMMARIZER_ERROR);
 	}
 
@@ -856,16 +861,13 @@ GetLatestLSN(TimeLineID *tli)
 static void
 ProcessWalSummarizerInterrupts(void)
 {
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		ProcessConfigFile(PGC_SIGHUP);
-	}
 
-	if (ShutdownRequestPending || !summarize_wal)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_AUX) || !summarize_wal)
 	{
 		ereport(DEBUG1,
 				errmsg_internal("WAL summarizer shutting down"));
@@ -873,7 +875,7 @@ ProcessWalSummarizerInterrupts(void)
 	}
 
 	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
+	if (InterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
 		ProcessLogMemoryContextInterrupt();
 }
 
@@ -1639,7 +1641,11 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+	(void) WaitInterrupt(INTERRUPT_GENERAL |
+						 INTERRUPT_SHUTDOWN_AUX |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
 						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
 						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 9678c01c5a3..d74ee8f5d0c 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -57,7 +57,6 @@
 #include "storage/interrupt.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
-#include "storage/procsignal.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -109,7 +108,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
 	/*
@@ -236,9 +235,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		/* Process any signals received recently */
 		ProcessMainLoopInterrupts();
 
@@ -264,7 +260,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_MAIN_LOOP_MASK,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 cur_timeout,
 							 WAIT_EVENT_WAL_WRITER_MAIN);
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 41137b4d373..3205efba6d4 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -237,7 +237,8 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical,
 		else
 			io_flag = WL_SOCKET_WRITEABLE;
 
-		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+		rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK |
+								   INTERRUPT_SHUTDOWN_AUX,
 								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
 								   PQsocket(conn->streamConn),
 								   0,
@@ -245,10 +246,7 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical,
 
 		/* Interrupted? */
 		if (rc & WL_INTERRUPT)
-		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
-		}
 
 		/* If socket is ready, advance the libpq state machine */
 		if (rc & io_flag)
@@ -848,7 +846,8 @@ libpqrcv_PQgetResult(PGconn *streamConn)
 		 * since we'll get interrupted by signals and can handle any
 		 * interrupts here.
 		 */
-		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+		rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK |
+								   INTERRUPT_SHUTDOWN_AUX,
 								   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
 								   WL_INTERRUPT,
 								   PQsocket(streamConn),
@@ -857,10 +856,7 @@ libpqrcv_PQgetResult(PGconn *streamConn)
 
 		/* Interrupted? */
 		if (rc & WL_INTERRUPT)
-		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(streamConn) == 0)
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 5d27b4d5886..6dbdad622a5 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -239,12 +239,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -714,7 +708,7 @@ ProcessParallelApplyInterrupts(void)
 {
 	CHECK_FOR_INTERRUPTS();
 
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 	{
 		ereport(LOG,
 				(errmsg("logical replication parallel apply worker for subscription \"%s\" has finished",
@@ -723,11 +717,8 @@ ProcessParallelApplyInterrupts(void)
 		proc_exit(0);
 	}
 
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
+	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		ProcessConfigFile(PGC_SIGHUP);
-	}
 }
 
 /* Parallel apply worker main loop. */
@@ -805,7 +796,10 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 				int			rc;
 
 				/* Wait for more work. */
-				rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+				rc = WaitInterrupt(INTERRUPT_CFI_MASK |
+								   INTERRUPT_SHUTDOWN_AUX |
+								   INTERRUPT_CONFIG_RELOAD |
+								   INTERRUPT_GENERAL,
 								   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								   1000L,
 								   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
@@ -844,9 +838,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -934,8 +927,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;
@@ -979,21 +971,6 @@ ParallelApplyWorkerMain(Datum main_arg)
 	Assert(false);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
-}
-
 /*
  * Process a single protocol message received from a single parallel apply
  * worker.
@@ -1058,7 +1035,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1091,7 +1068,7 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
+	ClearInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE);
 
 	foreach(lc, ParallelApplyWorkerPool)
 	{
@@ -1183,15 +1160,15 @@ pa_send_data(ParallelApplyWorkerInfo *winfo, Size nbytes, const void *data)
 		Assert(result == SHM_MQ_WOULD_BLOCK);
 
 		/* Wait before retrying. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   SHM_SEND_RETRY_INTERVAL_MS,
 						   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		if (startTime == 0)
@@ -1255,16 +1232,16 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo,
 			break;
 
 		/* Wait to be signalled. */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 10L,
 							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
 
-		/* Clear the interrupt flag so we don't spin. */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
+
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 }
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index ab33444a293..3fbb31df2d1 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -214,14 +214,14 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		 * interrupt about the worker attach.  But we don't expect to have to
 		 * wait long.
 		 */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 	}
 }
@@ -442,6 +442,7 @@ retry:
 	worker->relstate_lsn = InvalidXLogRecPtr;
 	worker->stream_fileset = NULL;
 	worker->leader_pid = is_parallel_apply_worker ? MyProcPid : InvalidPid;
+	worker->leader_pgprocno = is_parallel_apply_worker ? MyProcNumber : INVALID_PROC_NUMBER;
 	worker->parallel_apply = is_parallel_apply_worker;
 	worker->last_lsn = InvalidXLogRecPtr;
 	TIMESTAMP_NOBEGIN(worker->last_send_time);
@@ -522,7 +523,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, int interrupt)
 {
 	uint16		generation;
 
@@ -545,14 +546,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/* Recheck worker status. */
@@ -572,7 +573,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -586,14 +587,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		LWLockAcquire(LogicalRepWorkerLock, LW_SHARED);
@@ -615,7 +616,7 @@ logicalrep_worker_stop(Oid subid, Oid relid)
 	if (worker)
 	{
 		Assert(!isParallelApplyWorker(worker));
-		logicalrep_worker_stop_internal(worker, SIGTERM);
+		logicalrep_worker_stop_internal(worker, INTERRUPT_DIE);
 	}
 
 	LWLockRelease(LogicalRepWorkerLock);
@@ -662,13 +663,14 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 	 * Only stop the worker if the generation matches and the worker is alive.
 	 */
 	if (worker->generation == generation && worker->proc)
-		logicalrep_worker_stop_internal(worker, SIGINT);
+		logicalrep_worker_stop_internal(worker, INTERRUPT_QUERY_CANCEL);
 
 	LWLockRelease(LogicalRepWorkerLock);
 }
 
 /*
- * Wake up (using interrupt) any logical replication worker for specified sub/rel.
+ * Wake up (using INTERRUPT_GENERAL) any logical replication worker for
+ * specified sub/rel.
  */
 void
 logicalrep_worker_wakeup(Oid subid, Oid relid)
@@ -763,7 +765,7 @@ logicalrep_worker_detach(void)
 			LogicalRepWorker *w = (LogicalRepWorker *) lfirst(lc);
 
 			if (isParallelApplyWorker(w))
-				logicalrep_worker_stop_internal(w, SIGTERM);
+				logicalrep_worker_stop_internal(w, INTERRUPT_DIE);
 		}
 
 		LWLockRelease(LogicalRepWorkerLock);
@@ -793,6 +795,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;
 }
 
@@ -1218,22 +1221,20 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL |
-						   1 << INTERRUPT_SUBSCRIPTION_CHANGE,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK |
+						   INTERRUPT_CONFIG_RELOAD |
+						   INTERRUPT_SUBSCRIPTION_CHANGE |
+						   INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   wait_time,
 						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
-		}
-
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+				ProcessConfigFile(PGC_SIGHUP);
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 	}
 
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 505cf21c143..5d1f535e7c6 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -73,9 +73,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The slot sync worker's pid is needed by the startup process to shut it
- * down during promotion. The startup process shuts down the slot sync worker
- * and also sets stopSignaled=true to handle the race condition when the
+ * The slot sync worker's proc number is needed by the startup process to shut
+ * it down during promotion. The startup process shuts down the slot sync
+ * worker and also sets stopSignaled=true to handle the race condition when the
  * postmaster has not noticed the promotion yet and thus may end up restarting
  * the slot sync worker. If stopSignaled is set, the worker will exit in such a
  * case. The SQL function pg_sync_replication_slots() will also error out if
@@ -95,7 +95,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1113,7 +1113,7 @@ slotsync_reread_config(void)
 
 	Assert(sync_replication_slots);
 
-	ConfigReloadPending = false;
+	ClearInterrupt(INTERRUPT_CONFIG_RELOAD);
 	ProcessConfigFile(PGC_SIGHUP);
 
 	conninfo_changed = strcmp(old_primary_conninfo, PrimaryConnInfo) != 0;
@@ -1155,7 +1155,7 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 {
 	CHECK_FOR_INTERRUPTS();
 
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_DIE))
 	{
 		ereport(LOG,
 				errmsg("replication slot synchronization worker is shutting down on receiving SIGINT"));
@@ -1163,7 +1163,7 @@ ProcessSlotSyncInterrupts(WalReceiverConn *wrconn)
 		proc_exit(0);
 	}
 
-	if (ConfigReloadPending)
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1207,7 +1207,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
@@ -1253,7 +1253,9 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+	rc = WaitInterrupt(INTERRUPT_CFI_MASK |
+					   INTERRUPT_GENERAL |
+					   INTERRUPT_CONFIG_RELOAD,
 					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 					   sleep_ms,
 					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
@@ -1267,12 +1269,12 @@ wait_for_slot_activity(bool some_slot_updated)
  * Otherwise, advertise that a sync is in progress.
  */
 static void
-check_and_set_sync_info(pid_t worker_pid)
+check_and_set_sync_info(ProcNumber worker_procno)
 {
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
 	/* The worker pid must not be already assigned in SlotSyncCtx */
-	Assert(worker_pid == InvalidPid || SlotSyncCtx->pid == InvalidPid);
+	Assert(worker_procno == INVALID_PROC_NUMBER || SlotSyncCtx->procno == INVALID_PROC_NUMBER);
 
 	/*
 	 * Emit an error if startup process signaled the slot sync machinery to
@@ -1297,10 +1299,10 @@ check_and_set_sync_info(pid_t worker_pid)
 	SlotSyncCtx->syncing = true;
 
 	/*
-	 * Advertise the required PID so that the startup process can kill the
-	 * slot sync worker on promotion.
+	 * Advertise the required proc number so that the startup process can kill
+	 * the slot sync worker on promotion.
 	 */
-	SlotSyncCtx->pid = worker_pid;
+	SlotSyncCtx->procno = worker_procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1392,16 +1394,16 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
 	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGCHLD, SIG_DFL);
 
-	check_and_set_sync_info(MyProcPid);
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1522,7 +1524,7 @@ update_synced_slots_inactive_since(void)
 		return;
 
 	/* The slot sync worker or SQL function mustn't be running by now */
-	Assert((SlotSyncCtx->pid == InvalidPid) && !SlotSyncCtx->syncing);
+	Assert((SlotSyncCtx->procno == INVALID_PROC_NUMBER) && !SlotSyncCtx->syncing);
 
 	LWLockAcquire(ReplicationSlotControlLock, LW_SHARED);
 
@@ -1536,7 +1538,7 @@ update_synced_slots_inactive_since(void)
 			Assert(SlotIsLogical(s));
 
 			/* The slot must not be acquired by any process */
-			Assert(s->active_pid == 0);
+			Assert(s->active_proc == INVALID_PROC_NUMBER);
 
 			/* Use the same inactive_since time for all the slots. */
 			if (now == 0)
@@ -1559,7 +1561,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		worker_pid;
+	ProcNumber	worker_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1576,12 +1578,12 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	worker_pid = SlotSyncCtx->pid;
+	worker_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
-	if (worker_pid != InvalidPid)
-		kill(worker_pid, SIGINT);
+	if (worker_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_QUERY_CANCEL, worker_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1589,14 +1591,15 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK |
+						   INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		SpinLockAcquire(&SlotSyncCtx->mutex);
@@ -1674,7 +1677,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1724,7 +1727,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 {
 	PG_ENSURE_ERROR_CLEANUP(slotsync_failure_callback, PointerGetDatum(wrconn));
 	{
-		check_and_set_sync_info(InvalidPid);
+		check_and_set_sync_info(INVALID_PROC_NUMBER);
 
 		validate_remote_info(wrconn);
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 877fb7aca36..eb2a4993f17 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -211,7 +211,7 @@ wait_for_relation_state_change(Oid relid, char expected_state)
 		if (!worker)
 			break;
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
@@ -261,10 +261,10 @@ wait_for_worker_state_change(char expected_state)
 			break;
 
 		/*
-		 * Wait.  We expect to get an interrupt wakeup from the apply worker,
-		 * but use a timeout in case it dies without sending one.
+		 * Wait.  We expect to get an INTERRUPT_GENERAL wakeup from the apply
+		 * worker, but use a timeout in case it dies without sending one.
 		 */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
@@ -774,12 +774,10 @@ copy_read_data(void *outbuf, int minread, int maxread)
 		/*
 		 * Wait for more data or interrupt.
 		 */
-		(void) WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+		(void) WaitInterruptOrSocket(INTERRUPT_CFI_MASK,
 									 WL_SOCKET_READABLE | WL_INTERRUPT |
 									 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 									 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
-
-		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return bytesread;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 29d03af0ac5..71b93d4945f 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -3655,11 +3655,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();
@@ -3754,7 +3751,9 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 		else
 			wait_time = NAPTIME_PER_CYCLE;
 
-		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+		rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK |
+								   INTERRUPT_CONFIG_RELOAD |
+								   INTERRUPT_GENERAL,
 								   WL_SOCKET_READABLE | WL_INTERRUPT |
 								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 								   fd, wait_time,
@@ -3762,14 +3761,10 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 
 		if (rc & WL_INTERRUPT)
 		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
-		}
-
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
-			ProcessConfigFile(PGC_SIGHUP);
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+				ProcessConfigFile(PGC_SIGHUP);
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		if (rc & WL_TIMEOUT)
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index ca271330594..84be4a050f3 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -51,6 +51,7 @@
 #include "replication/slot.h"
 #include "replication/walsender_private.h"
 #include "storage/fd.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -224,6 +225,7 @@ ReplicationSlotsShmemInit(void)
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
 			/* everything else is zeroed by the memset above */
+			slot->active_proc = INVALID_PROC_NUMBER;
 			SpinLockInit(&slot->mutex);
 			LWLockInitialize(&slot->io_in_progress_lock,
 							 LWTRANCHE_REPLICATION_SLOT_IO);
@@ -402,7 +404,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	 * be doing that.  So it's safe to initialize the slot.
 	 */
 	Assert(!slot->in_use);
-	Assert(slot->active_pid == 0);
+	Assert(slot->active_proc == INVALID_PROC_NUMBER);
 
 	/* first initialize persistent data */
 	memset(&slot->data, 0, sizeof(ReplicationSlotPersistentData));
@@ -444,8 +446,8 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 
 	/* We can now mark the slot active, and that makes it our slot. */
 	SpinLockAcquire(&slot->mutex);
-	Assert(slot->active_pid == 0);
-	slot->active_pid = MyProcPid;
+	Assert(slot->active_proc == INVALID_PROC_NUMBER);
+	slot->active_proc = MyProcNumber;
 	SpinLockRelease(&slot->mutex);
 	MyReplicationSlot = slot;
 
@@ -559,7 +561,7 @@ void
 ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
-	int			active_pid;
+	ProcNumber	active_proc;
 
 	Assert(name != NULL);
 
@@ -600,15 +602,15 @@ retry:
 		 * to inactive_since in InvalidatePossiblyObsoleteSlot.
 		 */
 		SpinLockAcquire(&s->mutex);
-		if (s->active_pid == 0)
-			s->active_pid = MyProcPid;
-		active_pid = s->active_pid;
+		if (s->active_proc == INVALID_PROC_NUMBER)
+			s->active_proc = MyProcNumber;
+		active_proc = s->active_proc;
 		ReplicationSlotSetInactiveSince(s, 0, false);
 		SpinLockRelease(&s->mutex);
 	}
 	else
 	{
-		active_pid = MyProcPid;
+		active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
 	LWLockRelease(ReplicationSlotControlLock);
@@ -618,7 +620,7 @@ retry:
 	 * wait until the owning process signals us that it's been released, or
 	 * error out.
 	 */
-	if (active_pid != MyProcPid)
+	if (active_proc != MyProcNumber)
 	{
 		if (!nowait)
 		{
@@ -632,7 +634,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -690,7 +692,7 @@ ReplicationSlotRelease(void)
 	bool		is_logical = false; /* keep compiler quiet */
 	TimestampTz now = 0;
 
-	Assert(slot != NULL && slot->active_pid != 0);
+	Assert(slot != NULL && slot->active_proc != INVALID_PROC_NUMBER);
 
 	if (am_walsender)
 	{
@@ -736,7 +738,7 @@ ReplicationSlotRelease(void)
 		 * disconnecting, but wake up others that may be waiting for it.
 		 */
 		SpinLockAcquire(&slot->mutex);
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 		ReplicationSlotSetInactiveSince(slot, now, false);
 		SpinLockRelease(&slot->mutex);
 		ConditionVariableBroadcast(&slot->active_cv);
@@ -788,7 +790,7 @@ restart:
 			continue;
 
 		SpinLockAcquire(&s->mutex);
-		if ((s->active_pid == MyProcPid &&
+		if ((s->active_proc == MyProcNumber &&
 			 (!synced_only || s->data.synced)))
 		{
 			Assert(s->data.persistency == RS_TEMPORARY);
@@ -976,7 +978,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot)
 		bool		fail_softly = slot->data.persistency != RS_PERSISTENT;
 
 		SpinLockAcquire(&slot->mutex);
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 		SpinLockRelease(&slot->mutex);
 
 		/* wake up anyone waiting on this slot */
@@ -998,7 +1000,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot)
 	 * Also wake up processes waiting for it.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);
-	slot->active_pid = 0;
+	slot->active_proc = INVALID_PROC_NUMBER;
 	slot->in_use = false;
 	LWLockRelease(ReplicationSlotControlLock);
 	ConditionVariableBroadcast(&slot->active_cv);
@@ -1293,7 +1295,7 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 		/* count slots with spinlock held */
 		SpinLockAcquire(&s->mutex);
 		(*nslots)++;
-		if (s->active_pid != 0)
+		if (s->active_proc != INVALID_PROC_NUMBER)
 			(*nactive)++;
 		SpinLockRelease(&s->mutex);
 	}
@@ -1331,7 +1333,7 @@ restart:
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
-		int			active_pid;
+		ProcNumber	active_proc;
 
 		s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1353,11 +1355,11 @@ restart:
 		SpinLockAcquire(&s->mutex);
 		/* can't change while ReplicationSlotControlLock is held */
 		slotname = NameStr(s->data.name);
-		active_pid = s->active_pid;
-		if (active_pid == 0)
+		active_proc = s->active_proc;
+		if (active_proc == INVALID_PROC_NUMBER)
 		{
 			MyReplicationSlot = s;
-			s->active_pid = MyProcPid;
+			s->active_proc = MyProcNumber;
 		}
 		SpinLockRelease(&s->mutex);
 
@@ -1382,11 +1384,11 @@ restart:
 		 * XXX: We can consider shutting down the slot sync worker before
 		 * trying to drop synced temporary slots here.
 		 */
-		if (active_pid)
+		if (active_proc != INVALID_PROC_NUMBER)
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_IN_USE),
 					 errmsg("replication slot \"%s\" is active for PID %d",
-							slotname, active_pid)));
+							slotname, GetPGProcByNumber(active_proc)->pid)));
 
 		/*
 		 * To avoid duplicating ReplicationSlotDropAcquired() and to avoid
@@ -1721,7 +1723,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *invalidated)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		terminated = false;
 	TransactionId initial_effective_xmin = InvalidTransactionId;
@@ -1734,7 +1736,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 	{
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
-		int			active_pid = 0;
+		ProcNumber	active_proc = INVALID_PROC_NUMBER;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -1817,17 +1819,17 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		}
 
 		slotname = s->data.name;
-		active_pid = s->active_pid;
+		active_proc = s->active_proc;
 
 		/*
 		 * If the slot can be acquired, do so and mark it invalidated
 		 * immediately.  Otherwise we'll signal the owning process, below, and
 		 * retry.
 		 */
-		if (active_pid == 0)
+		if (active_proc == INVALID_PROC_NUMBER)
 		{
 			MyReplicationSlot = s;
-			s->active_pid = MyProcPid;
+			s->active_proc = MyProcNumber;
 			s->data.invalidated = invalidation_cause;
 
 			/*
@@ -1864,7 +1866,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 								&slot_idle_usecs);
 		}
 
-		if (active_pid != 0)
+		if (active_proc != INVALID_PROC_NUMBER)
 		{
 			/*
 			 * Prepare the sleep on the slot's condition variable before
@@ -1887,22 +1889,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers, which
+			 * do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
-					(void) SendProcSignal(active_pid,
-										  PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
-										  INVALID_PROC_NUMBER);
+					SendInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT,
+								  active_proc);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_DIE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 				terminated = true;
 				invalidation_cause_prev = invalidation_cause;
 			}
@@ -1937,7 +1942,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -2576,7 +2582,7 @@ RestoreSlotFromDisk(const char *name)
 		slot->candidate_restart_valid = InvalidXLogRecPtr;
 
 		slot->in_use = true;
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 
 		/*
 		 * Set the time since the slot has become inactive after loading the
@@ -2884,7 +2890,7 @@ StandbySlotsHaveCaughtup(XLogRecPtr wait_for_lsn, int elevel)
 		SpinLockAcquire(&slot->mutex);
 		restart_lsn = slot->data.restart_lsn;
 		invalidated = slot->data.invalidated != RS_INVAL_NONE;
-		inactive = slot->active_pid == 0;
+		inactive = slot->active_proc == INVALID_PROC_NUMBER;
 		SpinLockRelease(&slot->mutex);
 
 		if (invalidated)
@@ -2970,11 +2976,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 	{
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/* Exit if done waiting for every slot. */
 		if (StandbySlotsHaveCaughtup(wait_for_lsn, WARNING))
@@ -2984,6 +2987,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a wait to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 146eef5871e..6820b131465 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -20,6 +20,7 @@
 #include "replication/logical.h"
 #include "replication/slot.h"
 #include "replication/slotsync.h"
+#include "storage/proc.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/pg_lsn.h"
@@ -255,6 +256,7 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 	{
 		ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[slotno];
 		ReplicationSlot slot_contents;
+		int			active_pid;
 		Datum		values[PG_GET_REPLICATION_SLOTS_COLS];
 		bool		nulls[PG_GET_REPLICATION_SLOTS_COLS];
 		WALAvailability walstate;
@@ -267,6 +269,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 		/* Copy slot contents while holding spinlock, then examine at leisure */
 		SpinLockAcquire(&slot->mutex);
 		slot_contents = *slot;
+		if (slot_contents.active_proc != INVALID_PROC_NUMBER)
+			active_pid = GetPGProcByNumber(slot_contents.active_proc)->pid;
+		else
+			active_pid = 0;
 		SpinLockRelease(&slot->mutex);
 
 		memset(values, 0, sizeof(values));
@@ -291,10 +297,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 			values[i++] = ObjectIdGetDatum(slot_contents.data.database);
 
 		values[i++] = BoolGetDatum(slot_contents.data.persistency == RS_TEMPORARY);
-		values[i++] = BoolGetDatum(slot_contents.active_pid != 0);
+		values[i++] = BoolGetDatum(slot_contents.active_proc != INVALID_PROC_NUMBER);
 
-		if (slot_contents.active_pid != 0)
-			values[i++] = Int32GetDatum(slot_contents.active_pid);
+		if (slot_contents.active_proc != INVALID_PROC_NUMBER)
+			values[i++] = Int32GetDatum(active_pid);
 		else
 			nulls[i++] = true;
 
@@ -359,13 +365,13 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				 */
 				if (!XLogRecPtrIsInvalid(slot_contents.data.restart_lsn))
 				{
-					int			pid;
+					ProcNumber	procno;
 
 					SpinLockAcquire(&slot->mutex);
-					pid = slot->active_pid;
+					procno = slot->active_proc;
 					slot_contents.data.restart_lsn = slot->data.restart_lsn;
 					SpinLockRelease(&slot->mutex);
-					if (pid != 0)
+					if (procno != INVALID_PROC_NUMBER)
 					{
 						values[i++] = CStringGetTextDatum("unreserved");
 						walstate = WALAVAIL_UNRESERVED;
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 1c0d79b8568..444ef498cec 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -252,10 +252,10 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 * entitled to assume that an acknowledged commit is also replicated,
 		 * which might not be true. So in this case we issue a WARNING (which
 		 * some clients may be able to interpret) and shut off further output.
-		 * We do NOT reset ProcDiePending, so that the process will die after
+		 * We do NOT clear the interrupt bit, so that the process will die after
 		 * the commit is cleaned up.
 		 */
-		if (ProcDiePending)
+		if (InterruptPending(INTERRUPT_DIE))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -272,9 +272,8 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 * altogether is not helpful, so we just terminate the wait with a
 		 * suitable warning.
 		 */
-		if (QueryCancelPending)
+		if (ConsumeInterrupt(INTERRUPT_QUERY_CANCEL))
 		{
-			QueryCancelPending = false;
 			ereport(WARNING,
 					(errmsg("canceling wait for synchronous replication due to user request"),
 					 errdetail("The transaction has already committed locally, but might not have been replicated to the standby.")));
@@ -286,7 +285,9 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 * Wait on interrupt.  Any condition that should wake us up will set
 		 * the interrupt, so no need for timeout.
 		 */
-		rc = WaitInterrupt(1 << INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_GENERAL |
+						   INTERRUPT_DIE |
+						   INTERRUPT_QUERY_CANCEL,
 						   WL_INTERRUPT | WL_POSTMASTER_DEATH,
 						   -1,
 						   WAIT_EVENT_SYNC_REP);
@@ -297,7 +298,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_DIE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4bc23ae21a3..cee0fd2a08f 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -71,7 +71,6 @@
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
@@ -170,7 +169,7 @@ ProcessWalRcvInterrupts(void)
 	 */
 	CHECK_FOR_INTERRUPTS();
 
-	if (ShutdownRequestPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_AUX))
 	{
 		ereport(FATAL,
 				(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -285,7 +284,7 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
 
 	/* Reset some signals that are accepted by postmaster but not here */
@@ -462,9 +461,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 				/* Process any requests or signals received recently */
 				ProcessWalRcvInterrupts();
 
-				if (ConfigReloadPending)
+				if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				{
-					ConfigReloadPending = false;
 					ProcessConfigFile(PGC_SIGHUP);
 					/* recompute wakeup times */
 					now = GetCurrentTimestamp();
@@ -547,7 +545,10 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+				rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK |
+										   INTERRUPT_SHUTDOWN_AUX |
+										   INTERRUPT_CONFIG_RELOAD |
+										   INTERRUPT_GENERAL,
 										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
 										   WL_TIMEOUT | WL_INTERRUPT,
 										   wait_fd,
@@ -555,7 +556,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 										   WAIT_EVENT_WAL_RECEIVER_MAIN);
 				if (rc & WL_INTERRUPT)
 				{
-					ClearInterrupt(INTERRUPT_GENERAL);
 					ProcessWalRcvInterrupts();
 
 					if (walrcv->force_reply)
@@ -570,6 +570,7 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 						pg_memory_barrier();
 						XLogWalRcvSendReply(true, false);
 					}
+					ClearInterrupt(INTERRUPT_GENERAL);
 				}
 				if (rc & WL_TIMEOUT)
 				{
@@ -735,7 +736,9 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK |
+							 INTERRUPT_SHUTDOWN_AUX |
+							 INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
 							 0,
 							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 4cafa69b89c..5ac203ef4fd 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,7 +25,7 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
@@ -200,9 +200,9 @@ static volatile sig_atomic_t got_STOPPING = false;
 
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM. When
+ * set, the main loop is responsible for checking got_STOPPING and terminating
+ * when it's set (after streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -260,7 +260,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -1609,7 +1609,10 @@ ProcessPendingWrites(void)
 
 		/* Sleep until something happens or we time out */
 		WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime,
-				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
+				   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+				   INTERRUPT_CFI_MASK |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_GENERAL);
 
 		/* Clear any already-pending wakeups */
 		ClearInterrupt(INTERRUPT_GENERAL);
@@ -1617,9 +1620,8 @@ ProcessPendingWrites(void)
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -1629,7 +1631,7 @@ ProcessPendingWrites(void)
 			WalSndShutdown();
 	}
 
-	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
 	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
@@ -1823,9 +1825,8 @@ WalSndWaitForWal(XLogRecPtr loc)
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -1935,7 +1936,10 @@ WalSndWaitForWal(XLogRecPtr loc)
 
 		Assert(wait_event != 0);
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   INTERRUPT_CFI_MASK |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_GENERAL);
 	}
 
 	/* reactivate interrupt flag so WalSndLoop knows to continue */
@@ -2760,9 +2764,8 @@ WalSndLoop(WalSndSendDataCallback send_data)
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -2858,7 +2861,11 @@ WalSndLoop(WalSndSendDataCallback send_data)
 				wakeEvents |= WL_SOCKET_WRITEABLE;
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   INTERRUPT_CFI_MASK |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_GENERAL
+				);
 		}
 	}
 }
@@ -2896,6 +2903,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -2952,6 +2960,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3526,10 +3535,10 @@ WalSndRqstFileReload(void)
 }
 
 /*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
+ * Process INTERRUPT_WALSND_INIT_STOPPING interrupt
  */
 void
-HandleWalSndInitStopping(void)
+ProcessWalSndInitStopping(void)
 {
 	Assert(am_walsender);
 
@@ -3540,9 +3549,12 @@ HandleWalSndInitStopping(void)
 	 * standby, and then exit gracefully.
 	 */
 	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
+		RaiseInterrupt(INTERRUPT_DIE);
 	else
+	{
 		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_GENERAL);
+	}
 }
 
 /*
@@ -3568,7 +3580,7 @@ WalSndSignals(void)
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	pqsignal(SIGUSR1, SIG_IGN);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
 
@@ -3656,24 +3668,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
+	/* for condition variables */
+	interruptMask |= INTERRUPT_GENERAL;
+
 	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SendInterrupt(),
-	 * helping walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3721,16 +3737,16 @@ WalSndInitStopping(void)
 	for (i = 0; i < max_wal_senders; i++)
 	{
 		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
-		pid_t		pid;
+		ProcNumber	procno;
 
 		SpinLockAcquire(&walsnd->mutex);
-		pid = walsnd->pid;
+		procno = walsnd->pgprocno;
 		SpinLockRelease(&walsnd->mutex);
 
-		if (pid == 0)
+		if (procno == INVALID_PROC_NUMBER)
 			continue;
 
-		SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, INVALID_PROC_NUMBER);
+		SendInterrupt(INTERRUPT_WALSND_INIT_STOPPING, procno);
 	}
 }
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index c5bf4a70925..1bf0ec509ab 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -55,6 +55,7 @@
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
+#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "storage/standby.h"
@@ -2976,7 +2977,7 @@ BufferSync(int flags)
 		UnlockBufHdr(bufHdr, buf_state);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (InterruptPending(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3057,7 +3058,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (InterruptPending(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -5213,7 +5214,7 @@ LockBufferForCleanup(Buffer buffer)
 			 * deadlock_timeout for it.
 			 */
 			if (logged_recovery_conflict)
-				LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
+				LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN,
 									waitStart, GetCurrentTimestamp(),
 									NULL, false);
 
@@ -5263,7 +5264,7 @@ LockBufferForCleanup(Buffer buffer)
 				if (TimestampDifferenceExceeds(waitStart, now,
 											   DeadlockTimeout))
 				{
-					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
+					LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN,
 										waitStart, now, NULL, true);
 					logged_recovery_conflict = true;
 				}
@@ -5285,16 +5286,17 @@ LockBufferForCleanup(Buffer buffer)
 		}
 		else
 		{
-			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+			WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 						  WAIT_EVENT_BUFFER_PIN);
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other reasons as well.
+		 * We take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/ipc/interrupt.c b/src/backend/storage/ipc/interrupt.c
index 6e58f64b096..ac22555cc77 100644
--- a/src/backend/storage/ipc/interrupt.c
+++ b/src/backend/storage/ipc/interrupt.c
@@ -21,6 +21,7 @@
 #include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/waiteventset.h"
+#include "utils/resowner.h"
 
 /* A common WaitEventSet used to implement WaitInterrupt() */
 static WaitEventSet *InterruptWaitSet;
@@ -89,17 +90,17 @@ SwitchToSharedInterrupts(void)
  * Set an interrupt flag in this backend.
  */
 void
-RaiseInterrupt(InterruptType reason)
+RaiseInterrupt(uint32 interruptMask)
 {
 	uint32		old_pending;
 
-	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << reason);
+	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, interruptMask);
 
 	/*
 	 * If the process is currently blocked waiting for an interrupt to arrive,
 	 * and the interrupt wasn't already pending, wake it up.
 	 */
-	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
 		WakeupMyProc();
 }
 
@@ -110,7 +111,7 @@ RaiseInterrupt(InterruptType reason)
  * trust the contents of shared memory.
  */
 void
-SendInterrupt(InterruptType reason, ProcNumber pgprocno)
+SendInterrupt(uint32 interruptMask, ProcNumber pgprocno)
 {
 	PGPROC	   *proc;
 	uint32		old_pending;
@@ -120,13 +121,15 @@ SendInterrupt(InterruptType reason, ProcNumber pgprocno)
 	Assert(pgprocno < ProcGlobal->allProcCount);
 
 	proc = &ProcGlobal->allProcs[pgprocno];
-	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, 1 << reason);
+	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, interruptMask);
+
+	/* need a memory barrier here? Or is WakeupOtherProc() sufficient? */
 
 	/*
 	 * If the process is currently blocked waiting for an interrupt to arrive,
 	 * and the interrupt wasn't already pending, wake it up.
 	 */
-	if ((old_pending & (1 << reason | 1 << SLEEPING_ON_INTERRUPTS)) == 1 << SLEEPING_ON_INTERRUPTS)
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
 		WakeupOtherProc(proc);
 }
 
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index e4d5b944e12..e8b293f4fff 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -28,6 +28,7 @@
 #include "postmaster/autovacuum.h"
 #endif
 #include "storage/dsm.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "tcop/tcopprot.h"
 
@@ -175,9 +176,8 @@ proc_exit_prepare(int code)
 	 * close up shop already.  Note that the signal handlers will not set
 	 * these flags again, now that proc_exit_inprogress is set.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_DIE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 2e54c11f880..a877984b8c3 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -58,6 +58,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
+#include "storage/interrupt.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/acl.h"
@@ -3488,13 +3489,13 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  * Returns pid of the process signaled, or 0 if not found.
  */
 pid_t
-CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
+CancelVirtualTransaction(VirtualTransactionId vxid, InterruptType reason)
 {
-	return SignalVirtualTransaction(vxid, sigmode, true);
+	return SignalVirtualTransaction(vxid, reason, true);
 }
 
 pid_t
-SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
+SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason,
 						 bool conflictPending)
 {
 	ProcArrayStruct *arrayP = procArray;
@@ -3522,7 +3523,7 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
 				 * Kill the pid if it's still here. If not, that's what we
 				 * wanted so ignore any errors.
 				 */
-				(void) SendProcSignal(pid, sigmode, vxid.procNumber);
+				SendInterrupt(reason, vxid.procNumber);
 			}
 			break;
 		}
@@ -3656,7 +3657,7 @@ CountDBConnections(Oid databaseid)
  * CancelDBBackends --- cancel backends that are using specified database
  */
 void
-CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
+CancelDBBackends(Oid databaseid, InterruptType reason, bool conflictPending)
 {
 	ProcArrayStruct *arrayP = procArray;
 	int			index;
@@ -3671,20 +3672,17 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
 
 		if (databaseid == InvalidOid || proc->databaseId == databaseid)
 		{
-			VirtualTransactionId procvxid;
 			pid_t		pid;
 
-			GET_VXID_FROM_PGPROC(procvxid, *proc);
-
 			proc->recoveryConflictPending = conflictPending;
 			pid = proc->pid;
 			if (pid != 0)
 			{
 				/*
-				 * Kill the pid if it's still here. If not, that's what we
-				 * wanted so ignore any errors.
+				 * Cancel the backend if it's still here. If not, that's what
+				 * we wanted anyway.
 				 */
-				(void) SendProcSignal(pid, sigmode, procvxid.procNumber);
+				SendInterrupt(reason, pgprocno);
 			}
 		}
 	}
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 91557a7927e..82ea9e5cb8d 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -27,6 +27,7 @@
 #include "storage/condition_variable.h"
 #include "storage/interrupt.h"
 #include "storage/ipc.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -34,38 +35,30 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
- * (or greater) appears in the pss_barrierGeneration flag of every process,
- * we know that the message has been received everywhere.
+ * (or greater) appears in the pss_barrierGeneration flag of every process, we
+ * know that the message has been received everywhere.
  */
 typedef struct
 {
 	pg_atomic_uint32 pss_pid;
 	bool		pss_cancel_key_valid;
 	int32		pss_cancel_key;
-	volatile sig_atomic_t pss_signalFlags[NUM_PROCSIGNALS];
 	slock_t		pss_mutex;		/* protects the above fields */
 
 	/* Barrier-related fields (not protected by pss_mutex) */
@@ -104,7 +97,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -150,7 +142,6 @@ ProcSignalShmemInit(void)
 			pg_atomic_init_u32(&slot->pss_pid, 0);
 			slot->pss_cancel_key_valid = false;
 			slot->pss_cancel_key = 0;
-			MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
 			pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
 			pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
 			ConditionVariableInit(&slot->pss_barrierCV);
@@ -180,9 +171,6 @@ ProcSignalInit(bool cancel_key_valid, int32 cancel_key)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -230,9 +218,10 @@ CleanupProcSignalState(int status, Datum arg)
 	ProcSignalSlot *slot = MyProcSignalSlot;
 
 	/*
-	 * Clear MyProcSignalSlot, so that a SIGUSR1 received after this point
-	 * won't try to access it after it's no longer ours (and perhaps even
-	 * after we've unmapped the shared memory segment).
+	 * Clear MyProcSignalSlot, so that if INTERRUPT_BARRIER is received after
+	 * this point, ProcessProcSignalBarrier() won't try to access it after
+	 * it's no longer ours (and perhaps even after we've unmapped the shared
+	 * memory segment).
 	 */
 	Assert(MyProcSignalSlot != NULL);
 	MyProcSignalSlot = NULL;
@@ -268,73 +257,6 @@ CleanupProcSignalState(int status, Datum arg)
 	ConditionVariableBroadcast(&slot->pss_barrierCV);
 }
 
-/*
- * SendProcSignal
- *		Send a signal to a Postgres process
- *
- * Providing procNumber is optional, but it will speed up the operation.
- *
- * On success (a signal was sent), zero is returned.
- * On error, -1 is returned, and errno is set (typically to ESRCH or EPERM).
- *
- * Not to be confused with ProcSendSignal
- */
-int
-SendProcSignal(pid_t pid, ProcSignalReason reason, ProcNumber procNumber)
-{
-	volatile ProcSignalSlot *slot;
-
-	if (procNumber != INVALID_PROC_NUMBER)
-	{
-		Assert(procNumber < NumProcSignalSlots);
-		slot = &ProcSignal->psh_slot[procNumber];
-
-		SpinLockAcquire(&slot->pss_mutex);
-		if (pg_atomic_read_u32(&slot->pss_pid) == pid)
-		{
-			/* Atomically set the proper flag */
-			slot->pss_signalFlags[reason] = true;
-			SpinLockRelease(&slot->pss_mutex);
-			/* Send signal */
-			return kill(pid, SIGUSR1);
-		}
-		SpinLockRelease(&slot->pss_mutex);
-	}
-	else
-	{
-		/*
-		 * procNumber not provided, so search the array using pid.  We search
-		 * the array back to front so as to reduce search overhead.  Passing
-		 * INVALID_PROC_NUMBER means that the target is most likely an
-		 * auxiliary process, which will have a slot near the end of the
-		 * array.
-		 */
-		int			i;
-
-		for (i = NumProcSignalSlots - 1; i >= 0; i--)
-		{
-			slot = &ProcSignal->psh_slot[i];
-
-			if (pg_atomic_read_u32(&slot->pss_pid) == pid)
-			{
-				SpinLockAcquire(&slot->pss_mutex);
-				if (pg_atomic_read_u32(&slot->pss_pid) == pid)
-				{
-					/* Atomically set the proper flag */
-					slot->pss_signalFlags[reason] = true;
-					SpinLockRelease(&slot->pss_mutex);
-					/* Send signal */
-					return kill(pid, SIGUSR1);
-				}
-				SpinLockRelease(&slot->pss_mutex);
-			}
-		}
-	}
-
-	errno = ESRCH;
-	return -1;
-}
-
 /*
  * EmitProcSignalBarrier
  *		Send a signal to every Postgres process
@@ -379,8 +301,8 @@ EmitProcSignalBarrier(ProcSignalBarrierType type)
 		pg_atomic_add_fetch_u64(&ProcSignal->psh_barrierGeneration, 1);
 
 	/*
-	 * Signal all the processes, so that they update their advertised barrier
-	 * generation.
+	 * Send an interrupt to all the processes, so that they update their
+	 * advertised barrier generation.
 	 *
 	 * Concurrency is not a problem here. Backends that have exited don't
 	 * matter, and new backends that have joined since we entered this
@@ -391,26 +313,8 @@ EmitProcSignalBarrier(ProcSignalBarrierType type)
 	 * backends that need to update state - but they won't actually need to
 	 * change any state.
 	 */
-	for (int i = NumProcSignalSlots - 1; i >= 0; i--)
-	{
-		volatile ProcSignalSlot *slot = &ProcSignal->psh_slot[i];
-		pid_t		pid = pg_atomic_read_u32(&slot->pss_pid);
-
-		if (pid != 0)
-		{
-			SpinLockAcquire(&slot->pss_mutex);
-			pid = pg_atomic_read_u32(&slot->pss_pid);
-			if (pid != 0)
-			{
-				/* see SendProcSignal for details */
-				slot->pss_signalFlags[PROCSIG_BARRIER] = true;
-				SpinLockRelease(&slot->pss_mutex);
-				kill(pid, SIGUSR1);
-			}
-			else
-				SpinLockRelease(&slot->pss_mutex);
-		}
-	}
+	for (ProcNumber pgprocno = 0; pgprocno < NumProcSignalSlots; pgprocno++)
+		SendInterrupt(INTERRUPT_BARRIER, pgprocno);
 
 	return generation;
 }
@@ -469,23 +373,6 @@ WaitForProcSignalBarrier(uint64 generation)
 	pg_memory_barrier();
 }
 
-/*
- * Handle receipt of an interrupt indicating a global barrier event.
- *
- * All the actual work is deferred to ProcessProcSignalBarrier(), because we
- * cannot safely access the barrier generation inside the signal handler as
- * 64bit atomics might use spinlock based emulation, even for reads. As this
- * routine only gets called when PROCSIG_BARRIER is sent that won't cause a
- * lot of unnecessary work.
- */
-static void
-HandleProcSignalBarrierInterrupt(void)
-{
-	InterruptPending = true;
-	ProcSignalBarrierPending = true;
-	/* interrupt will be raised by procsignal_sigusr1_handler */
-}
-
 /*
  * Perform global barrier related interrupt checking.
  *
@@ -501,12 +388,12 @@ ProcessProcSignalBarrier(void)
 	uint64		shared_gen;
 	volatile uint32 flags;
 
-	Assert(MyProcSignalSlot);
-
 	/* Exit quickly if there's no work to do. */
-	if (!ProcSignalBarrierPending)
+	if (!ConsumeInterrupt(INTERRUPT_BARRIER))
+		return;
+
+	if (MyProcSignalSlot == NULL)
 		return;
-	ProcSignalBarrierPending = false;
 
 	/*
 	 * It's not unlikely to process multiple barriers at once, before the
@@ -635,86 +522,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
-
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index b752dccee1d..2b177c52cf3 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,9 +4,10 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process interrupts are used for synchronization.  Only the sender may send,
- * and only the receiver may receive.  This is intended to allow a user
- * backend to communicate with worker backends that it has registered.
+ * INTERRUPT_GENERAL interrupts are used for synchronization.  Only the
+ * sender may send, and only the receiver may receive.  This is intended to
+ * allow a user backend to communicate with worker backends that it has
+ * registered.
  *
  * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
@@ -49,7 +50,7 @@
  * interrupt to the counterparty, the counterparty must be certain to see the
  * change after waking up.  Since SendInterrupt begins with a memory barrier
  * and ClearInterrupt ends with one, this should be OK.
- * XXX: No explicit memory barriers in SendIntterupt/ClearInterrupt, do we need to add some?
+ * 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.
@@ -343,16 +344,15 @@ shm_mq_send(shm_mq_handle *mqh, Size nbytes, const void *data, bool nowait,
  * Write a message into a shared message queue, gathered from multiple
  * addresses.
  *
- * When nowait = false, we'll wait on our process interrupt when the ring
- * buffer fills up, and then continue writing once the receiver has drained
- * some data.  The process interrupt is cleared after each wait.
+ * When nowait = false, we'll wait when the ring buffer fills up, and then
+ * continue writing once the receiver has drained some data.
  *
- * When nowait = true, we do not manipulate the state of the process interrupt;
- * instead, if the buffer becomes full, we return SHM_MQ_WOULD_BLOCK.  In
- * this case, the caller should call this function again, with the same
- * arguments, each time the process interrupt is set.  (Once begun, the sending
- * of a message cannot be aborted except by detaching from the queue; changing
- * the length or payload will corrupt the queue.)
+ * When nowait = true, if the buffer becomes full, we return
+ * SHM_MQ_WOULD_BLOCK.  In this case, the caller should call this function
+ * again, with the same arguments, each time the INTERRUPT_GENERAL interrupt
+ * is set.  (Once begun, the sending of a message cannot be aborted except by
+ * detaching from the queue; changing the length or payload will corrupt the
+ * queue.)
  *
  * When force_flush = true, we immediately update the shm_mq's mq_bytes_written
  * and notify the receiver (if it is already attached).  Otherwise, we don't
@@ -559,16 +559,15 @@ shm_mq_sendv(shm_mq_handle *mqh, shm_mq_iovec *iov, int iovcnt, bool nowait,
  * while still allowing longer messages.  In either case, the return value
  * remains valid until the next receive operation is performed on the queue.
  *
- * When nowait = false, we'll wait on our process interrupt when the ring
- * buffer is empty and we have not yet received a full message.  The sender
- * will set our process interrupt after more data has been written, and we'll
- * resume processing.  Each call will therefore return a complete message
- * (unless the sender detaches the queue).
+ * When nowait = false, we'll when the ring buffer is empty and we have not
+ * yet received a full message.  The sender will send us INTERRUPT_GENERAL
+ * after more data has been written, and we'll resume processing.  Each call
+ * will therefore return a complete message (unless the sender detaches the
+ * queue).
  *
- * When nowait = true, we do not manipulate the state of the process
- * interrupt; instead, whenever the buffer is empty and we need to read from
- * it, we return SHM_MQ_WOULD_BLOCK.  In this case, the caller should call
- * this function again after the process interrupt has been set.
+ * When nowait = true, we do not wait when the buffer is empty and return
+ * SHM_MQ_WOULD_BLOCK instead.  In this case, the caller should call this
+ * function again after INTERRUPT_GENERAL has been set.
  */
 shm_mq_result
 shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
@@ -1017,7 +1016,8 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 * interrupt at top of loop, because setting an already-set
 			 * interrupt is much cheaper than setting one that has been reset.
 			 */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 								 WAIT_EVENT_MESSAGE_QUEUE_SEND);
 
 			/* Clear the interrupt so we don't spin. */
@@ -1163,14 +1163,15 @@ shm_mq_receive_bytes(shm_mq_handle *mqh, Size bytes_needed, bool nowait,
 		 * loop, because setting an already-set interrupt is much cheaper than
 		 * setting one that has been cleared.
 		 */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 							 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
 
+		/* Handle standard interrupts that may have occurred while we were waiting. */
+		CHECK_FOR_INTERRUPTS();
+
 		/* Clear the interrupt so we don't spin. */
 		ClearInterrupt(INTERRUPT_GENERAL);
-
-		/* An interrupt may have occurred while we were waiting. */
-		CHECK_FOR_INTERRUPTS();
 	}
 }
 
@@ -1252,14 +1253,15 @@ shm_mq_wait_internal(shm_mq *mq, PGPROC **ptr, BackgroundWorkerHandle *handle)
 		}
 
 		/* Wait to be signaled. */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 							 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
 
+		/* Handle standard interrupts that may have occurred while we were waiting. */
+		CHECK_FOR_INTERRUPTS();
+
 		/* Clear the interrupt so we don't spin. */
 		ClearInterrupt(INTERRUPT_GENERAL);
-
-		/* An interrupt may have occurred while we were waiting. */
-		CHECK_FOR_INTERRUPTS();
 	}
 
 	return result;
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index d77b4f44ad5..265a14684f7 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -205,7 +205,7 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 waittime,
 							 WAIT_EVENT_BACKEND_TERMINATION);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 3c112daf001..7df37fca0b3 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -20,25 +20,8 @@
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and raise the
- * INTERRUPT_GENERAL. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * raising INTERRUPT_GENERAL.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	RaiseInterrupt(INTERRUPT_GENERAL);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,12 +131,12 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	while (InterruptPending(INTERRUPT_SINVAL_CATCHUP))
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
 		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
+		 * INTERRUPT_SINVAL_CATCHUP flag.  If we are inside a transaction we
 		 * can just call AcceptInvalidationMessages() to do this.  If we
 		 * aren't, we start and immediately end a transaction; the call to
 		 * AcceptInvalidationMessages() happens down inside transaction start.
@@ -190,12 +148,12 @@ ProcessCatchupInterrupt(void)
 		 */
 		if (IsTransactionOrTransactionBlock())
 		{
-			elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt inside transaction");
 			AcceptInvalidationMessages();
 		}
 		else
 		{
-			elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt outside transaction");
 			StartTransactionCommand();
 			CommitTransactionCommand();
 		}
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index 2da91738c32..880e776633d 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -18,10 +18,10 @@
 #include <unistd.h>
 
 #include "miscadmin.h"
+#include "storage/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -564,7 +564,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -657,8 +657,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -669,8 +669,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 76b2996f327..73ce6daaade 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -71,13 +71,13 @@ static volatile sig_atomic_t got_standby_delay_timeout = false;
 static volatile sig_atomic_t got_standby_lock_timeout = false;
 
 static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
-												   ProcSignalReason reason,
+												   InterruptType reason,
 												   uint32 wait_event_info,
 												   bool report_waiting);
-static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
+static void SendRecoveryConflictWithBufferPin(InterruptType reason);
 static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
 static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
-static const char *get_recovery_conflict_desc(ProcSignalReason reason);
+static const char *get_recovery_conflict_desc(InterruptType reason);
 
 /*
  * InitRecoveryTransactionEnvironment
@@ -271,7 +271,7 @@ WaitExceedsMaxStandbyDelay(uint32 wait_event_info)
  * to be resolved or not.
  */
 void
-LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
+LogRecoveryConflict(InterruptType reason, TimestampTz wait_start,
 					TimestampTz now, VirtualTransactionId *wait_list,
 					bool still_waiting)
 {
@@ -358,7 +358,7 @@ LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
  */
 static void
 ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
-									   ProcSignalReason reason, uint32 wait_event_info,
+									   InterruptType reason, uint32 wait_event_info,
 									   bool report_waiting)
 {
 	TimestampTz waitStart = 0;
@@ -489,7 +489,7 @@ ResolveRecoveryConflictWithSnapshot(TransactionId snapshotConflictHorizon,
 	backends = GetConflictingVirtualXIDs(snapshotConflictHorizon,
 										 locator.dbOid);
 	ResolveRecoveryConflictWithVirtualXIDs(backends,
-										   PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
+										   INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT,
 										   WAIT_EVENT_RECOVERY_CONFLICT_SNAPSHOT,
 										   true);
 
@@ -560,7 +560,7 @@ ResolveRecoveryConflictWithTablespace(Oid tsid)
 	temp_file_users = GetConflictingVirtualXIDs(InvalidTransactionId,
 												InvalidOid);
 	ResolveRecoveryConflictWithVirtualXIDs(temp_file_users,
-										   PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
+										   INTERRUPT_RECOVERY_CONFLICT_TABLESPACE,
 										   WAIT_EVENT_RECOVERY_CONFLICT_TABLESPACE,
 										   true);
 }
@@ -581,7 +581,7 @@ ResolveRecoveryConflictWithDatabase(Oid dbid)
 	 */
 	while (CountDBBackends(dbid) > 0)
 	{
-		CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE, true);
+		CancelDBBackends(dbid, INTERRUPT_RECOVERY_CONFLICT_DATABASE, true);
 
 		/*
 		 * Wait awhile for them to die so that we avoid flooding an
@@ -665,7 +665,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		 * because the caller, WaitOnLock(), has already reported that.
 		 */
 		ResolveRecoveryConflictWithVirtualXIDs(backends,
-											   PROCSIG_RECOVERY_CONFLICT_LOCK,
+											   INTERRUPT_RECOVERY_CONFLICT_LOCK,
 											   PG_WAIT_LOCK | locktag.locktag_type,
 											   false);
 	}
@@ -697,10 +697,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 	}
 
 	/* Wait to be signaled by the release of the Relation Lock */
-	WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+	WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+				  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 				  PG_WAIT_LOCK | locktag.locktag_type);
-	ClearInterrupt(INTERRUPT_GENERAL);
 	CHECK_FOR_INTERRUPTS();
+	ClearInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * Exit if ltime is reached. Then all the backends holding conflicting
@@ -727,7 +728,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		while (VirtualTransactionIdIsValid(*backends))
 		{
 			SignalVirtualTransaction(*backends,
-									 PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+									 INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
 									 false);
 			backends++;
 		}
@@ -749,10 +750,11 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		 * until the relation locks are released or ltime is reached.
 		 */
 		got_standby_deadlock_timeout = false;
-		WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+		WaitInterrupt(INTERRUPT_CFI_MASK |
+					  INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 					  PG_WAIT_LOCK | locktag.locktag_type);
-		ClearInterrupt(INTERRUPT_GENERAL);
 		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 cleanup:
@@ -778,9 +780,10 @@ cleanup:
  * We either resolve conflicts immediately or set a timeout to wake us at
  * the limit of our patience.
  *
- * Resolve conflicts by sending a PROCSIG signal to all backends to check if
- * they hold one of the buffer pins that is blocking Startup process. If so,
- * those backends will take an appropriate error action, ERROR or FATAL.
+ * Resolve conflicts by sending INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN to all
+ * backends to check if they hold one of the buffer pins that is blocking
+ * Startup process. If so, those backends will take an appropriate error
+ * action, ERROR or FATAL.
  *
  * We also must check for deadlocks.  Deadlocks occur because if queries
  * wait on a lock, that must be behind an AccessExclusiveLock, which can only
@@ -809,7 +812,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 		/*
 		 * We're already behind, so clear a path as quickly as possible.
 		 */
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+		SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN);
 	}
 	else
 	{
@@ -844,17 +847,20 @@ ResolveRecoveryConflictWithBufferPin(void)
 	 * We assume that only UnpinBuffer() and the timeout requests established
 	 * above can wake us up here. WakeupRecovery() called by walreceiver or
 	 * SIGHUP signal handler, etc cannot do that because it uses the different
-	 * interrupt flag. FIXME: seems like a shaky assumption. WakeupRecovery()
-	 * indeed uses a different interrupt flag (different latch earlier), but
-	 * the signal handler??
+	 * interrupt flag.
+	 *
+	 * FIXME: seems like a shaky assumption. WakeupRecovery() indeed uses a
+	 * different interrupt flag (different latch earlier), but the signal
+	 * handler??
 	 */
-	WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+	WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+				  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 				  WAIT_EVENT_BUFFER_PIN);
-	ClearInterrupt(INTERRUPT_GENERAL);
 	CHECK_FOR_INTERRUPTS();
+	ClearInterrupt(INTERRUPT_GENERAL);
 
 	if (got_standby_delay_timeout)
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+		SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN);
 	else if (got_standby_deadlock_timeout)
 	{
 		/*
@@ -870,7 +876,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 		 * not be so harmful because the period that the buffer is kept pinned
 		 * is basically no so long. But we should fix this?
 		 */
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+		SendRecoveryConflictWithBufferPin(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 	}
 
 	/*
@@ -885,10 +891,10 @@ ResolveRecoveryConflictWithBufferPin(void)
 }
 
 static void
-SendRecoveryConflictWithBufferPin(ProcSignalReason reason)
+SendRecoveryConflictWithBufferPin(InterruptType reason)
 {
-	Assert(reason == PROCSIG_RECOVERY_CONFLICT_BUFFERPIN ||
-		   reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+	Assert(reason == INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN ||
+		   reason == INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 
 	/*
 	 * We send signal to all backends to ask them if they are holding the
@@ -947,6 +953,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -956,6 +963,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -965,6 +973,7 @@ void
 StandbyLockTimeoutHandler(void)
 {
 	got_standby_lock_timeout = true;
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1489,31 +1498,31 @@ LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs,
 
 /* Return the description of recovery conflict */
 static const char *
-get_recovery_conflict_desc(ProcSignalReason reason)
+get_recovery_conflict_desc(InterruptType reason)
 {
 	const char *reasonDesc = _("unknown reason");
 
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN:
 			reasonDesc = _("recovery conflict on buffer pin");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_LOCK:
 			reasonDesc = _("recovery conflict on lock");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE:
 			reasonDesc = _("recovery conflict on tablespace");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT:
 			reasonDesc = _("recovery conflict on snapshot");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT:
 			reasonDesc = _("recovery conflict on replication slot");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			reasonDesc = _("recovery conflict on buffer deadlock");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case INTERRUPT_RECOVERY_CONFLICT_DATABASE:
 			reasonDesc = _("recovery conflict on database");
 			break;
 		default:
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 819dce62252..2fe74e3fe70 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -1101,7 +1101,7 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 			 * synchronization so that if an interrupt bit is set after this,
 			 * the setter will wake us up.
 			 */
-			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << SLEEPING_ON_INTERRUPTS);
+			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
 			already_pending = ((old_mask & set->interrupt_mask) != 0);
 
 			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
@@ -1176,7 +1176,7 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 
 	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
 	if (sleeping_flag_armed)
-		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) 1 << SLEEPING_ON_INTERRUPTS));
+		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
 
 #ifndef WIN32
 	waiting = false;
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index ef792103835..35cacc06b5b 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -161,7 +161,8 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * Wait for interrupt.  (If we're awakened for some other reason, the
 		 * code below will cope anyway.)
 		 */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+							 wait_events, cur_timeout, wait_event_info);
 
 		/* Clear the flag before examining the state of the wait list. */
 		ClearInterrupt(INTERRUPT_GENERAL);
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 11ae421ad68..53b90cc3492 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -1577,10 +1577,11 @@ GetSafeSnapshot(Snapshot origSnapshot)
 				 SxactIsROUnsafe(MySerializableXact)))
 		{
 			LWLockRelease(SerializableXactHashLock);
-			WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+			WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 						  WAIT_EVENT_SAFE_SNAPSHOT);
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 		}
 		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 34a525ae607..6a15138d8f8 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -1393,7 +1393,7 @@ ProcSleep(LOCALLOCK *locallock)
 					 * because the startup process here has already waited
 					 * longer than deadlock_timeout.
 					 */
-					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
+					LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_LOCK,
 										standbyWaitStart, now,
 										cnt > 0 ? vxids : NULL, true);
 					logged_recovery_conflict = true;
@@ -1402,9 +1402,9 @@ ProcSleep(LOCALLOCK *locallock)
 		}
 		else
 		{
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 								 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
-			ClearInterrupt(INTERRUPT_GENERAL);
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
@@ -1412,6 +1412,7 @@ ProcSleep(LOCALLOCK *locallock)
 				got_deadlock_timeout = false;
 			}
 			CHECK_FOR_INTERRUPTS();
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
@@ -1658,7 +1659,7 @@ ProcSleep(LOCALLOCK *locallock)
 	/*
 	 * Disable the timers, if they are still running.  As in LockErrorCleanup,
 	 * we must preserve the LOCK_TIMEOUT indicator flag: if a lock timeout has
-	 * already caused QueryCancelPending to become set, we want the cancel to
+	 * already raised INTERRUPT_QUERY_CANCEL, we want the cancel to
 	 * be reported as a lock timeout, not a user cancel.
 	 */
 	if (!InHotStandby)
@@ -1682,7 +1683,7 @@ ProcSleep(LOCALLOCK *locallock)
 	 * startup process waited longer than deadlock_timeout for it.
 	 */
 	if (InHotStandby && logged_recovery_conflict)
-		LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
+		LogRecoveryConflict(INTERRUPT_RECOVERY_CONFLICT_LOCK,
 							standbyWaitStart, GetCurrentTimestamp(),
 							NULL, false);
 
@@ -1881,10 +1882,6 @@ CheckDeadLockAlert(void)
 	 * did. Back then got_deadlock_timeout wasn't yet set... It's unlikely
 	 * that this ever would be a problem, but raising an interrupt again is
 	 * cheap.
-	 *
-	 * Note that, when this function runs inside procsignal_sigusr1_handler(),
-	 * the handler function raises the interrupt again after the interrupt is
-	 * raised here.
 	 */
 	RaiseInterrupt(INTERRUPT_GENERAL);
 	errno = save_errno;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9a8db57195e..e943a841651 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -67,6 +67,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/sinval.h"
+#include "storage/standby.h"
 #include "tcop/fastpath.h"
 #include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
@@ -154,10 +155,6 @@ static const char *userDoption = NULL;	/* -D switch */
 static bool EchoQuery = false;	/* -E switch */
 static bool UseSemiNewlineNewline = false;	/* -j switch */
 
-/* whether or not, and why, we were canceled by conflict with recovery */
-static volatile sig_atomic_t RecoveryConflictPending = false;
-static volatile sig_atomic_t RecoveryConflictPendingReasons[NUM_PROCSIGNALS];
-
 /* reused buffer to pass to SendRowDescriptionMessage() */
 static MemoryContext row_description_context = NULL;
 static StringInfoData row_description_buf;
@@ -496,42 +493,64 @@ ReadCommand(StringInfo inBuf)
  * false if about to read or done reading.
  *
  * Must preserve errno!
+ *
+ * Returns interrupt mask to use for waiting
  */
-void
+uint32
 ProcessClientReadInterrupt(bool blocked)
 {
 	int			save_errno = errno;
+	uint32		interruptMask = 0;
 
 	if (DoingCommandRead)
 	{
 		/* Check for general interrupts that arrived before/while reading */
+		interruptMask |= INTERRUPT_CFI_MASK;
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
+		interruptMask |= INTERRUPT_SINVAL_CATCHUP;
+		if (InterruptPending(INTERRUPT_SINVAL_CATCHUP))
 			ProcessCatchupInterrupt();
 
-		/* Process notify interrupts, if any */
-		if (notifyInterruptPending)
-			ProcessNotifyInterrupt(true);
-	}
-	else if (ProcDiePending)
-	{
 		/*
-		 * We're dying.  If there is no data available to read, then it's safe
-		 * (and sane) to handle that now.  If we haven't tried to read yet,
-		 * make sure the interrupt flag is set, so that if there is no data
-		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the interrupt flag is set, as we might've undesirably
-		 * cleared it while reading.
+		 * If we are truly idle, ie. *not* inside a transaction block matching
+		 * the conditions in PostgresMain(), there are a few interrupts things
+		 * that we process immediately.
 		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			RaiseInterrupt(INTERRUPT_GENERAL);
+		if (!IsTransactionOrTransactionBlock())
+		{
+			interruptMask |= INTERRUPT_ASYNC_NOTIFY;
+			if (InterruptPending(INTERRUPT_ASYNC_NOTIFY))
+				ProcessNotifyInterrupt(true);
+
+			interruptMask |= INTERRUPT_IDLE_STATS_TIMEOUT;
+			if (InterruptPending(INTERRUPT_IDLE_STATS_TIMEOUT))
+			{
+				ClearInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+				pgstat_report_stat(true);
+			}
+		}
+	}
+	else
+	{
+		interruptMask |= INTERRUPT_DIE;
+		if (InterruptPending(INTERRUPT_DIE))
+		{
+			/*
+			 * We're dying.  If there is no data available to read, then it's safe
+			 * (and sane) to handle that now.  If we haven't tried to read yet,
+			 * don't clear the interrupt flag is set, so that if there is no data
+			 * then we'll come back here and die.
+			 */
+			if (blocked)
+				CHECK_FOR_INTERRUPTS();
+		}
 	}
 
 	errno = save_errno;
+
+	return interruptMask;
 }
 
 /*
@@ -543,12 +562,12 @@ ProcessClientReadInterrupt(bool blocked)
  *
  * Must preserve errno!
  */
-void
+uint32
 ProcessClientWriteInterrupt(bool blocked)
 {
 	int			save_errno = errno;
 
-	if (ProcDiePending)
+	if (InterruptPending(INTERRUPT_DIE))
 	{
 		/*
 		 * We're dying.  If it's not possible to write, then we should handle
@@ -579,11 +598,11 @@ ProcessClientWriteInterrupt(bool blocked)
 				CHECK_FOR_INTERRUPTS();
 			}
 		}
-		else
-			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
+
+	return INTERRUPT_DIE;
 }
 
 /*
@@ -2528,29 +2547,29 @@ errdetail_abort(void)
  * Add an errdetail() line showing conflict source.
  */
 static int
-errdetail_recovery_conflict(ProcSignalReason reason)
+errdetail_recovery_conflict(InterruptType reason)
 {
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN:
 			errdetail("User was holding shared buffer pin for too long.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_LOCK:
 			errdetail("User was holding a relation lock for too long.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE:
 			errdetail("User was or might have been using tablespace that must be dropped.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT:
 			errdetail("User query might have needed to see row versions that must be removed.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT:
 			errdetail("User was using a logical replication slot that must be invalidated.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			errdetail("User transaction caused buffer deadlock with recovery.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case INTERRUPT_RECOVERY_CONFLICT_DATABASE:
 			errdetail("User was connected to a database that must be dropped.");
 			break;
 		default:
@@ -3005,17 +3024,11 @@ die(SIGNAL_ARGS)
 {
 	/* Don't joggle the elbow of proc_exit */
 	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
+		RaiseInterrupt(INTERRUPT_DIE);
 
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the interrupt */
-	RaiseInterrupt(INTERRUPT_GENERAL);
-
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
 	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
@@ -3033,17 +3046,7 @@ die(SIGNAL_ARGS)
 void
 StatementCancelHandler(SIGNAL_ARGS)
 {
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the interrupt */
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 /* signal handler for floating point exception */
@@ -3060,27 +3063,14 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to check for a particular type of
- * recovery conflict.  Runs in a SIGUSR1 handler.
- */
-void
-HandleRecoveryConflictInterrupt(ProcSignalReason reason)
-{
-	RecoveryConflictPendingReasons[reason] = true;
-	RecoveryConflictPending = true;
-	InterruptPending = true;
-	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
-}
-
-/*
- * Check one individual conflict reason.
+ * Process one individual conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
+ProcessRecoveryConflictInterrupt(InterruptType reason)
 {
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 
 			/*
 			 * If we aren't waiting for a lock we can never deadlock.
@@ -3091,21 +3081,21 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			/* Intentional fall through to check wait for pin */
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN:
 
 			/*
-			 * If PROCSIG_RECOVERY_CONFLICT_BUFFERPIN is requested but we
+			 * If INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN is requested but we
 			 * aren't blocking the Startup process there is nothing more to
 			 * do.
 			 *
-			 * When PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested,
+			 * When INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested,
 			 * if we're waiting for locks and the startup process is not
 			 * waiting for buffer pin (i.e., also waiting for locks), we set
 			 * the flag so that ProcSleep() will check for deadlocks.
 			 */
 			if (!HoldingBufferPinThatDelaysRecovery())
 			{
-				if (reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
+				if (reason == INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
 					GetStartupBufferPinWaitBufId() < 0)
 					CheckDeadLockAlert();
 				return;
@@ -3116,9 +3106,9 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			/* Intentional fall through to error handling */
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case INTERRUPT_RECOVERY_CONFLICT_LOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE:
+		case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT:
 
 			/*
 			 * If we aren't in a transaction any longer then ignore.
@@ -3128,14 +3118,14 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT:
 
 			/*
 			 * If we're not in a subtransaction then we are OK to throw an
 			 * ERROR to resolve the conflict.  Otherwise drop through to the
 			 * FATAL case.
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT is a special case that
+			 * RECOVERY_CONFLICT_LOGICALSLOT is a special case that
 			 * always throws an ERROR (ie never promotes to FATAL), though it
 			 * still has to respect QueryCancelHoldoffCount, so it shares this
 			 * code path.  Logical decoding slots are only acquired while
@@ -3145,17 +3135,17 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			 * intercept an error before the replication slot is released.
 			 *
 			 * XXX other times that we can throw just an ERROR *may* be
-			 * PROCSIG_RECOVERY_CONFLICT_LOCK if no locks are held in parent
+			 * RECOVERY_CONFLICT_LOCK if no locks are held in parent
 			 * transactions
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by
+			 * RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by
 			 * parent transactions and the transaction is not
 			 * transaction-snapshot mode
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_TABLESPACE if no temp files or
+			 * RECOVERY_CONFLICT_TABLESPACE if no temp files or
 			 * cursors open in parent transactions
 			 */
-			if (reason == PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT ||
+			if (reason == INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT ||
 				!IsSubTransaction())
 			{
 				/*
@@ -3179,12 +3169,10 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 					if (QueryCancelHoldoffCount != 0)
 					{
 						/*
-						 * Re-arm and defer this interrupt until later.  See
-						 * similar code in ProcessInterrupts().
+						 * Leave the interrupt set to defer this interrupt
+						 * until later.  See similar code in
+						 * ProcessInterrupts().
 						 */
-						RecoveryConflictPendingReasons[reason] = true;
-						RecoveryConflictPending = true;
-						InterruptPending = true;
 						return;
 					}
 
@@ -3207,7 +3195,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			/* Intentional fall through to session cancel */
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case INTERRUPT_RECOVERY_CONFLICT_DATABASE:
 
 			/*
 			 * Retrying is not possible because the database is dropped, or we
@@ -3216,7 +3204,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			 */
 			pgstat_report_recovery_conflict(reason);
 			ereport(FATAL,
-					(errcode(reason == PROCSIG_RECOVERY_CONFLICT_DATABASE ?
+					(errcode(reason == INTERRUPT_RECOVERY_CONFLICT_DATABASE ?
 							 ERRCODE_DATABASE_DROPPED :
 							 ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("terminating connection due to conflict with recovery"),
@@ -3230,60 +3218,37 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 	}
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
-{
-	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
-	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-	Assert(RecoveryConflictPending);
-
-	RecoveryConflictPending = false;
-
-	for (ProcSignalReason reason = PROCSIG_RECOVERY_CONFLICT_FIRST;
-		 reason <= PROCSIG_RECOVERY_CONFLICT_LAST;
-		 reason++)
-	{
-		if (RecoveryConflictPendingReasons[reason])
-		{
-			RecoveryConflictPendingReasons[reason] = false;
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
-}
-
 /*
  * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
  *
  * If an interrupt condition is pending, and it's safe to service it,
  * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
+ * INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask) 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
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts is
+ * guaranteed to clear the interrupt bits in CheckForInterruptsMask 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)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert (InterruptHoldoffCount == 0);
+	Assert (CritSectionCount == 0);
+
+	/*
+	 * TODO: it'd be good for performance to read the atomic
+	 * MyPendingInterrupts variable just once in this function
+	 */
+
+	if (ConsumeInterrupt(INTERRUPT_WALSND_INIT_STOPPING))
+		ProcessWalSndInitStopping();
 
-	if (ProcDiePending)
+	if (ConsumeInterrupt(INTERRUPT_DIE))
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3322,10 +3287,8 @@ ProcessInterrupts(void)
 					 errmsg("terminating connection due to administrator command")));
 	}
 
-	if (CheckClientConnectionPending)
+	if (ConsumeInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT))
 	{
-		CheckClientConnectionPending = false;
-
 		/*
 		 * Check for lost connection and re-arm, if still configured, but not
 		 * if we've arrived back at DoingCommandRead state.  We don't want to
@@ -3335,16 +3298,16 @@ ProcessInterrupts(void)
 		if (!DoingCommandRead && client_connection_check_interval > 0)
 		{
 			if (!pq_check_connection())
-				ClientConnectionLost = true;
+				RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
 			else
 				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
 									 client_connection_check_interval);
 		}
 	}
 
-	if (ClientConnectionLost)
+	if (InterruptPending(INTERRUPT_CLIENT_CONNECTION_LOST))
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps QueryCancel */
 		LockErrorCleanup();
 		/* don't send to client, we already know the connection to be dead. */
 		whereToSendOutput = DestNone;
@@ -3361,25 +3324,18 @@ ProcessInterrupts(void)
 	 *
 	 * See similar logic in ProcessRecoveryConflictInterrupts().
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
+	if (InterruptPending(INTERRUPT_QUERY_CANCEL) && 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.)
+		 * Leave the interrupt set so that we process the cancel request as
+		 * soon as we're done reading the message.
 		 */
-		InterruptPending = true;
 	}
-	else if (QueryCancelPending)
+	else if (ConsumeInterrupt(INTERRUPT_QUERY_CANCEL))
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3433,10 +3389,28 @@ ProcessInterrupts(void)
 		}
 	}
 
-	if (RecoveryConflictPending)
-		ProcessRecoveryConflictInterrupts();
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_DATABASE))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_DATABASE);
+
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_TABLESPACE))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_TABLESPACE);
+
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOCK))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOCK);
+
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT);
 
-	if (IdleInTransactionSessionTimeoutPending)
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT);
+
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN);
+
+	if (ConsumeInterrupt(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
+		ProcessRecoveryConflictInterrupt(INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+
+	if (ConsumeInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT))
 	{
 		/*
 		 * If the GUC has been reset to zero, ignore the signal.  This is
@@ -3444,7 +3418,6 @@ ProcessInterrupts(void)
 		 * interrupt.  We need to unset the flag before the injection point,
 		 * otherwise we could loop in interrupts checking.
 		 */
-		IdleInTransactionSessionTimeoutPending = false;
 		if (IdleInTransactionSessionTimeout > 0)
 		{
 			INJECTION_POINT("idle-in-transaction-session-timeout");
@@ -3454,10 +3427,9 @@ ProcessInterrupts(void)
 		}
 	}
 
-	if (TransactionTimeoutPending)
+	if (ConsumeInterrupt(INTERRUPT_TRANSACTION_TIMEOUT))
 	{
 		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
 		if (TransactionTimeout > 0)
 		{
 			INJECTION_POINT("transaction-timeout");
@@ -3467,10 +3439,9 @@ ProcessInterrupts(void)
 		}
 	}
 
-	if (IdleSessionTimeoutPending)
+	if (ConsumeInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT))
 	{
 		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
 		if (IdleSessionTimeout > 0)
 		{
 			INJECTION_POINT("idle-session-timeout");
@@ -3480,28 +3451,17 @@ ProcessInterrupts(void)
 		}
 	}
 
-	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
-	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
+	if (InterruptPending(INTERRUPT_BARRIER))
 		ProcessProcSignalBarrier();
 
-	if (ParallelMessagePending)
+	if (ConsumeInterrupt(INTERRUPT_PARALLEL_MESSAGE))
 		ProcessParallelMessages();
 
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
+	if (ConsumeInterrupt(INTERRUPT_PARALLEL_APPLY_MESSAGE))
 		ProcessParallelApplyMessages();
+
+	if (ConsumeInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT))
+		ProcessLogMemoryContextInterrupt();
 }
 
 /*
@@ -4204,7 +4164,7 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 
@@ -4378,7 +4338,7 @@ PostgresMain(const char *dbname, const char *username)
 		 * forgetting a timeout cancel.
 		 */
 		disable_all_timeouts(false);	/* do first to avoid race condition */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 		idle_in_transaction_timeout_enabled = false;
 		idle_session_timeout_enabled = false;
 
@@ -4563,7 +4523,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 (InterruptPending(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4649,7 +4609,7 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect when
 		 * it's called when DoingCommandRead is set, so check for interrupts
 		 * before resetting DoingCommandRead.
 		 */
@@ -4660,11 +4620,8 @@ PostgresMain(const char *dbname, const char *username)
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index 05a8ccfdb75..b19489706ba 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -17,7 +17,7 @@
 
 #include "postgres.h"
 
-#include "storage/procsignal.h"
+#include "storage/interrupt.h"
 #include "utils/pgstat_internal.h"
 #include "utils/timestamp.h"
 
@@ -78,7 +78,7 @@ pgstat_report_autovac(Oid dboid)
  * Report a Hot Standby recovery conflict.
  */
 void
-pgstat_report_recovery_conflict(int reason)
+pgstat_report_recovery_conflict(InterruptType reason)
 {
 	PgStat_StatDBEntry *dbentry;
 
@@ -90,31 +90,33 @@ pgstat_report_recovery_conflict(int reason)
 
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case INTERRUPT_RECOVERY_CONFLICT_DATABASE:
 
 			/*
 			 * Since we drop the information about the database as soon as it
 			 * replicates, there is no point in counting these conflicts.
 			 */
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case INTERRUPT_RECOVERY_CONFLICT_TABLESPACE:
 			dbentry->conflict_tablespace++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_LOCK:
 			dbentry->conflict_lock++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT:
 			dbentry->conflict_snapshot++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN:
 			dbentry->conflict_bufferpin++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT:
 			dbentry->conflict_logicalslot++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			dbentry->conflict_startup_deadlock++;
 			break;
+		default:
+			elog(LOG, "unexpected recovery conflict reason %u", (unsigned int) reason);
 	}
 }
 
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 396c2f223b4..7aa6fa304c1 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -265,7 +265,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -294,14 +293,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 160ea59d94b..5b33ec8d762 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -403,11 +403,10 @@ pg_sleep(PG_FUNCTION_ARGS)
 		else
 			break;
 
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 delay_ms,
 							 WAIT_EVENT_PG_SLEEP);
-		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 79bc999cf26..7a7090a23b9 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -28,21 +28,12 @@
 
 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;
 
+volatile uint32 CheckForInterruptsMask = 0;
+
 int			MyProcPid;
 pg_time_t	MyStartTime;
 TimestampTz MyStartTimestamp;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index e54b5b39396..0147549f525 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1382,41 +1382,31 @@ LockTimeoutHandler(void)
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	RaiseInterrupt(INTERRUPT_GENERAL);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index 93994190c71..d3220e419e6 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -370,12 +370,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on
-	 * INTERRUPT_GENERAL.
-	 */
-	RaiseInterrupt(INTERRUPT_GENERAL);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 5233a65aaa0..63cee06a12a 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1260,27 +1260,11 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
+ * Any backend that participates in standard interrupt handling must arrange
  * to call this function if we see LogMemoryContextPending set.
  * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
  * the target process for logging of memory contexts is a backend.
@@ -1288,7 +1272,7 @@ HandleLogMemoryContextInterrupt(void)
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
+	ClearInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
 
 	/*
 	 * Use LOG_SERVER_ONLY to prevent this message from being sent to the
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index a257d0a8ee6..b7bdada3646 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -14,8 +14,6 @@
 #ifndef PARALLEL_H
 #define PARALLEL_H
 
-#include <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 ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index f75c3df9556..50773ddb0e2 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index f88ae8137d6..e76a90c83af 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -209,7 +209,7 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+			rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK,
 									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
 									   PQsocket(conn),
 									   0,
@@ -217,10 +217,7 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 
 			/* Interrupted? */
 			if (rc & WL_INTERRUPT)
-			{
-				ClearInterrupt(INTERRUPT_GENERAL);
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -341,7 +338,7 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitInterruptOrSocket(1 << INTERRUPT_GENERAL,
+		rc = WaitInterruptOrSocket(INTERRUPT_CFI_MASK,
 								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
 								   WL_SOCKET_READABLE,
 								   PQsocket(conn),
@@ -350,10 +347,7 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 
 		/* Interrupted? */
 		if (rc & WL_INTERRUPT)
-		{
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -436,12 +430,10 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitInterruptOrSocket(1 << INTERRUPT_GENERAL, waitEvents,
+			WaitInterruptOrSocket(INTERRUPT_CFI_MASK, waitEvents,
 								  PQcancelSocket(cancel_conn),
 								  cur_timeout, PG_WAIT_CLIENT);
 
-			ClearInterrupt(INTERRUPT_GENERAL);
-
 			CHECK_FOR_INTERRUPTS();
 		}
 exit:	;
diff --git a/src/include/libpq/pqmq.h b/src/include/libpq/pqmq.h
index 348b4494333..ae02aacfaa1 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -17,7 +17,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f5b773b79e5..949b5a59252 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,6 +28,9 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
+#ifndef FRONTEND
+#include "storage/interrupt.h"
+#endif
 
 #define InvalidPid				(-1)
 
@@ -59,7 +62,7 @@
  *
  * 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.
+ * possible that it will just clear InterruptPending and return. XXX
  *
  * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
  * interrupt needs to be serviced, without trying to do so immediately.
@@ -86,64 +89,70 @@
  *****************************************************************************/
 
 /* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
 /* these are marked volatile because they are examined by signal handlers: */
+/* FIXME: is that still true, do these still need to be volatile? */
 extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
 extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
 extern PGDLLIMPORT volatile uint32 CritSectionCount;
 
+/* If you would call ProcessInterrupts now, it could handle these interrupts */
+/* XXX: volatile still needed? */
+extern PGDLLIMPORT volatile uint32 CheckForInterruptsMask;
+
 /* in tcop/postgres.c */
 extern void ProcessInterrupts(void);
 
 /* Test whether an interrupt is pending */
 #ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
 #else
-#define INTERRUPTS_PENDING_CONDITION() \
+#define INTERRUPTS_PENDING_CONDITION(mask) \
 	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? pgwin32_dispatch_queued_signals() : 0, \
-	 unlikely(InterruptPending))
+	 unlikely(InterruptPending(mask)))
 #endif
 
 /* Service interrupt, if one is pending and it's safe to service it now */
 #define CHECK_FOR_INTERRUPTS() \
 do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask) && CritSectionCount == 0) \
 		ProcessInterrupts(); \
 } while(0)
 
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
+/* Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'? */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & ~CheckForInterruptsMask) == 0 && CritSectionCount == 0)
 
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
+#define HOLD_INTERRUPTS() \
+do { \
+	CheckForInterruptsMask &= ~INTERRUPT_CFI_MASK;	\
+	InterruptHoldoffCount++; \
+} while(0)
 
 #define RESUME_INTERRUPTS() \
 do { \
 	Assert(InterruptHoldoffCount > 0); \
 	InterruptHoldoffCount--; \
+	if (InterruptHoldoffCount == 0) \
+	{												  \
+		CheckForInterruptsMask |= INTERRUPT_CFI_MASK; \
+		if (QueryCancelHoldoffCount > 0) \
+			CheckForInterruptsMask &= ~INTERRUPT_QUERY_CANCEL; \
+	  } \
 } while(0)
 
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
+#define HOLD_CANCEL_INTERRUPTS()  \
+do { \
+	CheckForInterruptsMask &= ~INTERRUPT_QUERY_CANCEL;	\
+	QueryCancelHoldoffCount++; \
+} while(0)
 
 #define RESUME_CANCEL_INTERRUPTS() \
 do { \
 	Assert(QueryCancelHoldoffCount > 0); \
 	QueryCancelHoldoffCount--; \
+	if (QueryCancelHoldoffCount == 0 && InterruptHoldoffCount == 0) \
+		CheckForInterruptsMask |= INTERRUPT_QUERY_CANCEL;			\
 } while(0)
 
 #define START_CRIT_SECTION()  (CritSectionCount++)
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 4aad10b0b6d..be523d1ab3e 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -605,7 +605,7 @@ extern bool pgstat_tracks_io_op(BackendType bktype, IOObject io_object,
 
 extern void pgstat_drop_database(Oid databaseid);
 extern void pgstat_report_autovac(Oid dboid);
-extern void pgstat_report_recovery_conflict(int reason);
+extern void pgstat_report_recovery_conflict(InterruptType reason);
 extern void pgstat_report_deadlock(void);
 extern void pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount);
 extern void pgstat_report_checksum_failure(void);
diff --git a/src/include/postmaster/interrupt_handlers.h b/src/include/postmaster/interrupt_handlers.h
index b3a8c38353e..c6f75516c67 100644
--- a/src/include/postmaster/interrupt_handlers.h
+++ b/src/include/postmaster/interrupt_handlers.h
@@ -19,11 +19,6 @@
 #ifndef INTERRUPT_HANDLERS_H
 #define INTERRUPT_HANDLERS_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
 extern void ProcessMainLoopInterrupts(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 88912606e4d..8900d1ed92f 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <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);
@@ -23,7 +19,6 @@ extern void TablesyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index f5a24ccfbf2..b0ac9f70ae2 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -160,8 +160,8 @@ typedef struct ReplicationSlot
 	/* is this slot defined */
 	bool		in_use;
 
-	/* Who is streaming out changes for this slot? 0 in unused slots. */
-	pid_t		active_pid;
+	/* Who is streaming out changes for this slot? INVALID_PROC_NUMBER in unused slots. */
+	ProcNumber	active_proc;
 
 	/* any outstanding modifications? */
 	bool		just_dirtied;
@@ -187,7 +187,7 @@ typedef struct ReplicationSlot
 	/* is somebody performing io on this slot? */
 	LWLock		io_in_progress_lock;
 
-	/* Condition variable signaled when active_pid changes */
+	/* Condition variable signaled when active_proc changes */
 	ConditionVariable active_cv;
 
 	/* all the remaining data is only used for logical slots */
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index c3e8e191339..24026fcfe5c 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,7 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
+extern void ProcessWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 814b812432a..3f07fed7a35 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,7 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 30b2775952c..6781e9f6a6a 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -78,9 +78,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/interrupt.h b/src/include/storage/interrupt.h
index 8a880cd4c29..234be78bdd2 100644
--- a/src/include/storage/interrupt.h
+++ b/src/include/storage/interrupt.h
@@ -12,18 +12,40 @@
  * an action, or asynchronously by timer signal handlers, other signal
  * handlers or "sent" by other backends setting them directly.
  *
- * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is
- * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query
- * cancellation or idle session timeout. Well behaved backend code performs
- * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never
- * sleep using mechanisms other than the WaitEventSet mechanism or the more
- * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for
- * bounded short periods, eg LWLock waits), so they should react in good time.
+ *
+ * CHECK_FOR_INTERRUPTS()
+ * ----------------------
  *
  * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
- * consists of tasks that are safe to perform at most times.  They can be
- * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
+ * consists of tasks that are safe to perform at most times.  It includes
+ * things like query cancellation and idle session timeout.  Well behaved
+ * backend code performs CHECK_FOR_INTERRUPTS() periodically in long
+ * computations, and should never sleep using mechanisms other than the
+ * WaitEventSet mechanism or the more convenient WaitInterrupt /
+ * WaitSockerOrInterrupt functions (except for bounded short periods, eg
+ * LWLock waits), so they should react in good time.  They can be suppressed
+ * by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
+ *
+ * Other special interrupts are checked for explicitly.
+ *
+ * Standard Signal handlers
+ * ------------------------
+ *
+ * Responses to signals that are translated to interrupts are fairly varied
+ * and many types of backends have their own implementations, but we provide a
+ * few generic signal handlers and interrupt checks in
+ * postmaster/interrupt_handlers.c to facilitate code reuse.
  *
+ * Multiplexed INTERRUPT_GENERAL
+ * -----------------------------
+ *
+ * The INTERRUPT_GENERAL interrupt is multiplexed for many different purposes
+ * that don't warrant a dedicated interrupt bit.  Because it's reused for
+ * different purposes, waiters must tolerate receiving spurious interrupt
+ * wakeups.
+ *
+ * Waiting on an interrupt
+ * -----------------------
  *
  * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
  *
@@ -32,11 +54,11 @@
  *	   ClearInterrupt(INTERRUPT_GENERAL);
  *	   if (work to do)
  *		   Do Stuff();
- *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ *	   WaitInterrupt(INTERRUPT_GENERAL, ...);
  * }
  *
  * It's important to clear the interrupt *before* checking if there's work to
- * do. Otherwise, if someone sets the interrupt between the check and the
+ * do.  Otherwise, if someone sets the interrupt between the check and the
  * ClearInterrupt() call, you will miss it and Wait will incorrectly block.
  *
  * Another valid coding pattern looks like:
@@ -45,7 +67,7 @@
  * {
  *	   if (work to do)
  *		   Do Stuff(); // in particular, exit loop if some condition satisfied
- *	   WaitInterrupt(1 << INTERRUPT_GENERAL, ...);
+ *	   WaitInterrupt(INTERRUPT_GENERAL, ...);
  *	   ClearInterrupt(INTERRUPT_GENERAL);
  * }
  *
@@ -62,6 +84,7 @@
  * with nested loops that can consume different events, you can define your
  * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL.
  *
+ *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -76,9 +99,7 @@
 
 #include "port/atomics.h"
 #include "storage/procnumber.h"
-#include "storage/waiteventset.h"
-
-#include <signal.h>
+#include "storage/waiteventset.h"		/* WL_* are defined in waiteventset.h */
 
 extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
 
@@ -89,80 +110,219 @@ extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
 typedef enum
 {
 	/*
-	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
-	 * waiting for an interrupt. If it's set, the backend needs to be woken up
-	 * when a bit in the pending interrupts mask is set. It's used internally
-	 * by the interrupt machinery, and cannot be used directly in the public
-	 * functions. It's named differently to distinguish it from the actual
-	 * interrupt flags.
+	 * INTERRUPT_GENERAL is used as a general-purpose wakeup, multiplexed for
+	 * many reasons.
+	 */
+	INTERRUPT_GENERAL = 1 << 0,
+
+	/*
+	 * Because backends sitting idle will not be reading sinval events, we
+	 * need a way to give an idle backend a swift kick in the rear and make it
+	 * catch up before the sinval queue overflows and forces it to go through
+	 * a cache reset exercise.  This is done by sending
+	 * INTERRUPT_SINVAL_CATCHUP to any backend that gets too far behind.
+	 *
+	 * The interrupt is processed whenever starting to read from the client,
+	 * or when interrupted while doing so, ProcessClientReadInterrupt() will
+	 * call ProcessCatchupInterrupt().
 	 */
-	SLEEPING_ON_INTERRUPTS = 0,
+	INTERRUPT_SINVAL_CATCHUP = 1 << 1,
 
 	/*
-	 * INTERRUPT_GENERAL is multiplexed for many reasons, like query
-	 * cancellation termination requests, recovery conflicts, and config
-	 * reload requests.  Upon receiving INTERRUPT_GENERAL, you should call
-	 * CHECK_FOR_INTERRUPTS() to process those requests.  It is also used for
-	 * various other context-dependent purposes, but note that if it's used to
-	 * wake up for other reasons, you must still call CHECK_FOR_INTERRUPTS()
-	 * once per iteration.
+	 * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered
+	 * to LISTEN on any channels that they might have messages they need to
+	 * deliver to the frontent. It is also processed whenever starting to read
+	 * from the client or while doing so, but only when there is no
+	 * transaction in progress.
 	 */
-	INTERRUPT_GENERAL,
+	INTERRUPT_ASYNC_NOTIFY = 1 << 2,
+
+	/* Raised by timer while idle, to send a stats update */
+	INTERRUPT_IDLE_STATS_TIMEOUT = 1 << 3,
+
+	/* Config file reload is requested */
+	INTERRUPT_CONFIG_RELOAD = 1 << 4,
 
 	/*
-	 * INTERRUPT_RECOVERY_CONTINUE is used to wake up startup process, to tell
-	 * it that it should continue WAL replay. It's sent by WAL receiver when
-	 * more WAL arrives, or when promotion is requested.
+	 * INTERRUPT_RECOVERY_CONTINUE is used to wake up the startup process, to
+	 * tell it that it should continue WAL replay.  It's sent by WAL receiver
+	 * when more WAL arrives, or when promotion is requested.  We don't reuse
+	 * INTERRUPT_GENERAL for this, so that more WAL arriving doesn't wake up
+	 * the startup process excessively when we're waiting in other places,
+	 * like for recovery conflicts.
 	 */
-	INTERRUPT_RECOVERY_CONTINUE,
+	INTERRUPT_RECOVERY_CONTINUE = 1 << 5,
 
 	/* sent to logical replication launcher, when a subscription changes */
-	INTERRUPT_SUBSCRIPTION_CHANGE,
+	INTERRUPT_SUBSCRIPTION_CHANGE = 1 << 6,
+
+	/*
+	 * Many aux processes don't want to react to INTERRUPT_DIE in
+	 * CHECK_FOR_INTERRUPTS(), so they use a separate flag when shutdown is
+	 * requested.
+	 *
+	 * TODO: perhaps use INTERRUPT_DIE, but teach CHECK_FOR_INTERRUPTS() to
+	 * ignore it in aux processes, and remove it from CheckForInterruptsMask.
+	 * That would save one interrupt bit, and would make things more
+	 * consistent.
+	 */
+	INTERRUPT_SHUTDOWN_AUX = 1 << 7,
+
+	/*
+	 * Perform one last checkpoint, then shut down. Only used in the checkpointer
+	 * process.
+	 */
+	INTERRUPT_SHUTDOWN_XLOG = 1 << 8,
+
+	/*---- Interrupts handled by CHECK_FOR_INTERRUPTS() ----*/
+
+	/*
+	 * Backend has been requested to terminate
+	 *
+	 * This is raised by the SIGTERM signal handler, or can be sent directly
+	 * by another backend e.g. with pg_terminate_backend().
+	 */
+	INTERRUPT_DIE = 1 << 9,
+
+	/*
+	 * Cancel current query, if any.
+	 *
+	 * This is raised by the SIGTERM signal handler, or can be sent directly
+	 * by another backend e.g. with pg_cancel_backend(), or in response to a
+	 * query cancellation packet.
+	 */
+	INTERRUPT_QUERY_CANCEL = 1 << 10,
+
+	/* ask walsenders to prepare for shutdown  */
+	INTERRUPT_WALSND_INIT_STOPPING = 1 << 11,
+
+	/*
+	 * Recovery conflict reasons. These are sent by the startup process in hot
+	 * standby mode when a backend holds back the WAL replay for too long.
+	 */
+	INTERRUPT_RECOVERY_CONFLICT_DATABASE = 1 << 12,
+	INTERRUPT_RECOVERY_CONFLICT_TABLESPACE = 1 << 13,
+	INTERRUPT_RECOVERY_CONFLICT_LOCK = 1 << 14,
+	INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT = 1 << 15,
+	INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN = 1 << 16,
+	INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK = 1 << 17,
+	INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT = 1 << 18,
+
+	/* Raised by timers */
+	INTERRUPT_TRANSACTION_TIMEOUT = 1 << 19,
+	INTERRUPT_IDLE_SESSION_TIMEOUT = 1 << 20,
+	INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT = 1 << 21,
+	INTERRUPT_CLIENT_CHECK_TIMEOUT = 1 << 22,
+
+	/* Raised synchronously when the client connection is lost */
+	INTERRUPT_CLIENT_CONNECTION_LOST = 1 << 23,
+
+	/* Ask backend to log the memory contexts */
+	INTERRUPT_LOG_MEMORY_CONTEXT = 1 << 24,
+
+	/* Message from a cooperating parallel backend */
+	INTERRUPT_PARALLEL_MESSAGE = 1 << 25,
+
+	/* Message from a parallel apply worker */
+	INTERRUPT_PARALLEL_APPLY_MESSAGE = 1 << 26,
+
+	/* procsignal global barrier interrupt  */
+	INTERRUPT_BARRIER = 1 << 27,
+
+	/*---- end of interrupts handled by CHECK_FOR_INTERRUPTS() ----*/
+
+	/*
+	 * NOTE: InterruptTypes must fit in a 32-bit bitmask. (If we had efficient
+	 * 64-bit atomics on all platforms, we could easily go up to 64 bits)
+	 */
+
+	/*
+	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+	 * waiting for an interrupt. If it's set, the backend needs to be woken up
+	 * when a bit in the pending interrupts mask is set. It's used internally
+	 * by the interrupt machinery, and cannot be used directly in the public
+	 * functions. It's named differently to distinguish it from the actual
+	 * interrupt flags.
+	 */
+	SLEEPING_ON_INTERRUPTS = 1 << 31,
+
 } InterruptType;
 
-/*
- * Test an interrupt flag.
- */
-static inline bool
-InterruptIsPending(InterruptType reason)
-{
-	return (pg_atomic_read_u32(MyPendingInterrupts) & (1 << reason)) != 0;
-}
+/* This is the set of interrupts that are processed by CHECK_FOR_INTERRUPTS */
+#define INTERRUPT_CFI_MASK	(							\
+		INTERRUPT_DIE |									\
+		INTERRUPT_QUERY_CANCEL |						\
+		INTERRUPT_WALSND_INIT_STOPPING |				\
+		INTERRUPT_RECOVERY_CONFLICT_DATABASE |			\
+		INTERRUPT_RECOVERY_CONFLICT_TABLESPACE |		\
+		INTERRUPT_RECOVERY_CONFLICT_LOCK |				\
+		INTERRUPT_RECOVERY_CONFLICT_SNAPSHOT |			\
+		INTERRUPT_RECOVERY_CONFLICT_BUFFERPIN |			\
+		INTERRUPT_RECOVERY_CONFLICT_STARTUP_DEADLOCK |	\
+		INTERRUPT_RECOVERY_CONFLICT_LOGICALSLOT |		\
+		INTERRUPT_TRANSACTION_TIMEOUT |					\
+		INTERRUPT_IDLE_SESSION_TIMEOUT |				\
+		INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT |	\
+		INTERRUPT_CLIENT_CHECK_TIMEOUT |				\
+		INTERRUPT_CLIENT_CONNECTION_LOST |				\
+		INTERRUPT_LOG_MEMORY_CONTEXT |					\
+		INTERRUPT_PARALLEL_MESSAGE |					\
+		INTERRUPT_PARALLEL_APPLY_MESSAGE |				\
+		INTERRUPT_BARRIER								\
+		)
+
+/* This is the set of interrupts that are processed by ProcessStartupProcInterrupts */
+#define INTERRUPT_STARTUP_PROC_MASK	(			\
+		INTERRUPT_BARRIER |						\
+		INTERRUPT_DIE |							\
+		INTERRUPT_LOG_MEMORY_CONTEXT |			\
+		INTERRUPT_CONFIG_RELOAD					\
+		)
+
+/* This is the set of interrupts that are processed by ProcessMainLoopInterrupts */
+#define INTERRUPT_MAIN_LOOP_MASK	(			\
+		INTERRUPT_BARRIER |						\
+		INTERRUPT_SHUTDOWN_AUX |				\
+		INTERRUPT_LOG_MEMORY_CONTEXT |			\
+		INTERRUPT_CONFIG_RELOAD					\
+		)
 
 /*
- * Test an interrupt flag.
+ * Test an interrupt flag (or flags).
  */
 static inline bool
-InterruptsPending(uint32 mask)
+InterruptPending(uint32 interruptMask)
 {
-	return (pg_atomic_read_u32(MyPendingInterrupts) & (mask)) != 0;
+	pg_read_barrier();
+	return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0;
 }
 
 /*
- * Clear an interrupt flag.
+ * Clear an interrupt flag (or flags).
  */
 static inline void
-ClearInterrupt(InterruptType reason)
+ClearInterrupt(uint32 interruptMask)
 {
-	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~(1 << reason));
+	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~interruptMask);
+	pg_write_barrier();
 }
 
 /*
- * Test and clear an interrupt flag.
+ * Test and clear an interrupt flag (or flags).
  */
 static inline bool
-ConsumeInterrupt(InterruptType reason)
+ConsumeInterrupt(uint32 interruptMask)
 {
-	if (likely(!InterruptIsPending(reason)))
+	if (likely(!InterruptPending(interruptMask)))
 		return false;
 
-	ClearInterrupt(reason);
+	ClearInterrupt(interruptMask);
 
 	return true;
 }
 
-extern void RaiseInterrupt(InterruptType reason);
-extern void SendInterrupt(InterruptType reason, ProcNumber pgprocno);
+extern void RaiseInterrupt(uint32 interruptMask);
+extern void SendInterrupt(uint32 interruptMask, ProcNumber pgprocno);
 extern int	WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
 						  uint32 wait_event_info);
 extern int	WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index ef0b733ebe8..e8b8c0a653e 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -14,6 +14,7 @@
 #ifndef PROCARRAY_H
 #define PROCARRAY_H
 
+#include "storage/interrupt.h"
 #include "storage/lock.h"
 #include "storage/standby.h"
 #include "utils/relcache.h"
@@ -77,14 +78,14 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   bool excludeXmin0, bool allDbs, int excludeVacuum,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
-extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode);
-extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
+extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, InterruptType reason);
+extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason,
 									  bool conflictPending);
 
 extern bool MinimumActiveBackends(int min);
 extern int	CountDBBackends(Oid databaseid);
 extern int	CountDBConnections(Oid databaseid);
-extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
+extern void CancelDBBackends(Oid databaseid, InterruptType reason, bool conflictPending);
 extern int	CountUserBackends(Oid roleid);
 extern bool CountOtherDBBackends(Oid databaseId,
 								 int *nbackends, int *nprepared);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 022fd8ed933..8d917f1bec4 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,43 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-
-	/* Recovery conflict reasons */
-	PROCSIG_RECOVERY_CONFLICT_FIRST,
-	PROCSIG_RECOVERY_CONFLICT_DATABASE = PROCSIG_RECOVERY_CONFLICT_FIRST,
-	PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
-	PROCSIG_RECOVERY_CONFLICT_LOCK,
-	PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
-	PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
-	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
-	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
-	PROCSIG_RECOVERY_CONFLICT_LAST = PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT_LAST + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -63,16 +26,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(bool cancel_key_valid, int32 cancel_key);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, int32 cancelAuthCode);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 2463c0f9fac..cc627ae4853 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -125,16 +125,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 24e2f5082bc..960d08ca294 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -15,8 +15,8 @@
 #define STANDBY_H
 
 #include "datatype/timestamp.h"
+#include "storage/interrupt.h"
 #include "storage/lock.h"
-#include "storage/procsignal.h"
 #include "storage/relfilelocator.h"
 #include "storage/standbydefs.h"
 
@@ -43,7 +43,7 @@ extern void CheckRecoveryConflictDeadlock(void);
 extern void StandbyDeadLockHandler(void);
 extern void StandbyTimeoutHandler(void);
 extern void StandbyLockTimeoutHandler(void);
-extern void LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
+extern void LogRecoveryConflict(InterruptType reason, TimestampTz wait_start,
 								TimestampTz now, VirtualTransactionId *wait_list,
 								bool still_waiting);
 
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 60ececb785c..c9ab8136b53 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -27,7 +27,6 @@
 #include <signal.h>
 
 #include "storage/procnumber.h"
-#include "utils/resowner.h"
 
 /*
  * Bitmasks for events that may wake-up WaitInterrupt(),
@@ -82,7 +81,9 @@ extern void InitializeWaitEventSupport(void);
 extern HANDLE CreateSharedWakeupHandle(void);
 #endif
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+struct ResourceOwnerData;
+
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index a62367f7793..908f39217b0 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -72,9 +71,8 @@ extern void die(SIGNAL_ARGS);
 extern void quickdie(SIGNAL_ARGS) pg_attribute_noreturn();
 extern void StatementCancelHandler(SIGNAL_ARGS);
 extern void FloatExceptionHandler(SIGNAL_ARGS) pg_attribute_noreturn();
-extern void HandleRecoveryConflictInterrupt(ProcSignalReason reason);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
+extern uint32 ProcessClientReadInterrupt(bool blocked);
+extern uint32 ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 8abc26abce2..5dce202fe90 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 5f31bf7082b..aaf3a9ae199 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -263,6 +263,9 @@ wait_for_workers_to_become_ready(worker_state *wstate,
 	{
 		int			workers_ready;
 
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
+
 		/* If all the workers are ready, we have succeeded. */
 		SpinLockAcquire(&hdr->mutex);
 		workers_ready = hdr->workers_ready;
@@ -285,12 +288,10 @@ wait_for_workers_to_become_ready(worker_state *wstate,
 			we_bgworker_startup = WaitEventExtensionNew("TestShmMqBgWorkerStartup");
 
 		/* Wait to be signaled. */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 							 we_bgworker_startup);
 
-		/* Clear the interrupt flag so we don't spin. */
-		ClearInterrupt(INTERRUPT_GENERAL);
-
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
 	}
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index ce91d74ca20..f063cab9bad 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -171,6 +171,8 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 	{
 		bool		wait = true;
 
+		ClearInterrupt(INTERRUPT_GENERAL);
+
 		/*
 		 * If we haven't yet sent the message the requisite number of times,
 		 * try again to send it now.  Note that when shm_mq_send() returns
@@ -239,9 +241,9 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 			 * they have read or written data and therefore there may now be
 			 * work for us to do.
 			 */
-			(void) WaitInterrupt(1 << INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+			(void) WaitInterrupt(INTERRUPT_CFI_MASK | INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
 								 we_message_queue);
-			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index 5736620832f..84052a992ca 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -217,7 +217,9 @@ worker_spi_main(Datum main_arg)
 		 * is awakened if postmaster dies.  That way the background process
 		 * goes away immediately in an emergency.
 		 */
-		(void) WaitInterrupt(1 << INTERRUPT_GENERAL,
+		(void) WaitInterrupt(INTERRUPT_CFI_MASK |
+							 INTERRUPT_CONFIG_RELOAD |
+							 INTERRUPT_GENERAL,
 							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 							 worker_spi_naptime * 1000L,
 							 worker_spi_wait_event_main);
@@ -228,11 +230,8 @@ worker_spi_main(Datum main_arg)
 		/*
 		 * In case of a SIGHUP, just reload the configuration.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * Start a transaction on which we can run queries.  Note that each
-- 
2.39.5



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-03-06 16:43       ` Heikki Linnakangas <[email protected]>
  2025-07-15 15:50         ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-07-23 07:42         ` Re: Interrupts vs signals Joel Jacobson <[email protected]>
  1 sibling, 2 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2025-03-06 16:43 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 06/03/2025 02:47, Heikki Linnakangas wrote:
> Here's a new patch set. It includes the previous work, and also goes the 
> whole hog and replaces procsignals and many other signalling with 
> interrupts. It's based on Thomas's v3-0002-Redesign-interrupts-remove- 
> ProcSignals.patch much earlier in this thread.

And here's yet another version. It's the same at high level, but with a 
ton of little fixes.

One notable change is that I merged storage/interrupt.[ch] with 
postmaster/interrupt.[ch], so the awkwardness of having two files with 
same name is gone.

-- 
Heikki Linnakangas
Neon (https://neon.tech)


Attachments:

  [text/x-patch] v7-0001-Replace-Latches-with-Interrupts.patch (219.4K, ../../[email protected]/2-v7-0001-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 2614f4edda78169fa3f70b67f07032c770706a06 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Sat, 1 Mar 2025 00:18:03 +0200
Subject: [PATCH v7 1/4] Replace Latches with Interrupts
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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

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

This commit introduces two interrupt bits. INTERRUPT_GENERAL replaces
the general-purpose per-process latch. All code that previously set a
process's process latch now sets its INTERRUPT_GENERAL interrupt bit
instead.

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

More interrupt bits are planned for followup patches, to replace the
various ProcSignal bits, as well as ConfigReloadPending and
ShutdownRequestPending.

Reviewed-by: Thomas Munro, Andres Freund, Robert Haas, Álvaro Herrera
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 contrib/pg_prewarm/autoprewarm.c              |  19 +-
 contrib/postgres_fdw/connection.c             |  22 +-
 contrib/postgres_fdw/postgres_fdw.c           |   4 +-
 doc/src/sgml/bgworker.sgml                    |   2 +-
 doc/src/sgml/sources.sgml                     |   6 +-
 src/backend/access/heap/vacuumlazy.c          |  11 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/parallel.c         |  30 +-
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/access/transam/xlogfuncs.c        |  12 +-
 src/backend/access/transam/xlogrecovery.c     |  91 ++--
 src/backend/backup/basebackup_throttle.c      |  18 +-
 src/backend/commands/async.c                  |  19 +-
 src/backend/commands/vacuum.c                 |   4 +-
 src/backend/executor/nodeAppend.c             |   5 +-
 src/backend/executor/nodeGather.c             |   8 +-
 src/backend/libpq/auth.c                      |   9 +-
 src/backend/libpq/be-secure-gssapi.c          |  15 +-
 src/backend/libpq/be-secure-openssl.c         |   7 +-
 src/backend/libpq/be-secure.c                 |  29 +-
 src/backend/libpq/pqcomm.c                    |  29 +-
 src/backend/libpq/pqmq.c                      |   8 +-
 src/backend/postmaster/autovacuum.c           |  21 +-
 src/backend/postmaster/auxprocess.c           |   1 +
 src/backend/postmaster/bgworker.c             |  18 +-
 src/backend/postmaster/bgwriter.c             |  36 +-
 src/backend/postmaster/checkpointer.c         |  47 +--
 src/backend/postmaster/interrupt.c            | 260 +++++++++++-
 src/backend/postmaster/pgarch.c               |  26 +-
 src/backend/postmaster/postmaster.c           |  31 +-
 src/backend/postmaster/startup.c              |  11 +-
 src/backend/postmaster/syslogger.c            |  17 +-
 src/backend/postmaster/walsummarizer.c        |  23 +-
 src/backend/postmaster/walwriter.c            |  22 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |  32 +-
 .../replication/logical/applyparallelworker.c |  38 +-
 src/backend/replication/logical/launcher.c    |  53 +--
 src/backend/replication/logical/slotsync.c    |  22 +-
 src/backend/replication/logical/tablesync.c   |  35 +-
 src/backend/replication/logical/worker.c      |  24 +-
 src/backend/replication/syncrep.c             |  29 +-
 src/backend/replication/walreceiver.c         |  46 ++-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           |  26 +-
 src/backend/storage/buffer/bufmgr.c           |  10 +-
 src/backend/storage/buffer/freelist.c         |  27 +-
 src/backend/storage/ipc/Makefile              |   1 -
 src/backend/storage/ipc/latch.c               | 387 ------------------
 src/backend/storage/ipc/meson.build           |   1 -
 src/backend/storage/ipc/procsignal.c          |   6 +-
 src/backend/storage/ipc/shm_mq.c              | 102 ++---
 src/backend/storage/ipc/signalfuncs.c         |  11 +-
 src/backend/storage/ipc/sinval.c              |  10 +-
 src/backend/storage/ipc/standby.c             |  22 +-
 src/backend/storage/ipc/waiteventset.c        | 384 +++++++++--------
 src/backend/storage/lmgr/condition_variable.c |  34 +-
 src/backend/storage/lmgr/predicate.c          |   8 +-
 src/backend/storage/lmgr/proc.c               | 127 +++---
 src/backend/storage/sync/sync.c               |   6 +-
 src/backend/tcop/postgres.c                   |  24 +-
 src/backend/utils/adt/misc.c                  |  26 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/init/globals.c              |   9 -
 src/backend/utils/init/miscinit.c             |  55 +--
 src/backend/utils/init/postinit.c             |  11 +-
 src/backend/utils/misc/timeout.c              |   8 +-
 src/backend/utils/mmgr/mcxt.c                 |   2 +-
 src/include/access/parallel.h                 |   2 +
 src/include/libpq/libpq-be-fe-helpers.h       |  45 +-
 src/include/libpq/libpq.h                     |   4 +-
 src/include/miscadmin.h                       |   4 -
 src/include/postmaster/interrupt.h            | 151 ++++++-
 src/include/storage/latch.h                   | 140 -------
 src/include/storage/proc.h                    |  16 +-
 src/include/storage/waiteventset.h            |  35 +-
 src/port/pgsleep.c                            |   8 +-
 src/test/isolation/isolationtester.c          |   2 +-
 src/test/modules/test_shm_mq/setup.c          |   9 +-
 src/test/modules/test_shm_mq/test.c           |  13 +-
 src/test/modules/test_shm_mq/worker.c         |   3 +-
 src/test/modules/worker_spi/worker_spi.c      |  19 +-
 src/tools/pgindent/typedefs.list              |   2 +-
 82 files changed, 1386 insertions(+), 1527 deletions(-)
 delete mode 100644 src/backend/storage/ipc/latch.c
 delete mode 100644 src/include/storage/latch.h

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index b45755b3347..20dc7885d6a 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -38,7 +38,6 @@
 #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/smgr.h"
@@ -216,10 +215,10 @@ autoprewarm_main(Datum main_arg)
 		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(INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+								 -1L,
+								 PG_WAIT_EXTENSION);
 		}
 		else
 		{
@@ -243,14 +242,14 @@ 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(INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 delay_in_ms,
+								 PG_WAIT_EXTENSION);
 		}
 
 		/* Reset the latch, loop. */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	/*
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 8ef9702c05c..0c9fdeb92fd 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -27,7 +27,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "postmaster/interrupt.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -802,8 +802,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(ERROR, NULL, conn, false, sql);
@@ -1446,7 +1446,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))
@@ -1541,7 +1541,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))
@@ -1654,12 +1654,12 @@ 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(INTERRUPT_GENERAL,
+										   WL_INTERRUPT | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+										   PQsocket(conn),
+										   cur_timeout, pgfdw_we_cleanup_result);
+				ClearInterrupt(INTERRUPT_GENERAL);
 
 				CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 1131a8bf77e..31cd92f9e36 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7355,7 +7355,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 2c393385a91..3171054e55d 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -227,7 +227,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.
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index fa68d4d024a..fc642f5f713 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -989,19 +989,19 @@ 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_GENERAL);
 }
 </programlisting>
     </para>
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 3b91d02605a..45f25270dd8 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -150,6 +150,7 @@
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/autovacuum.h"
+#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
@@ -3242,11 +3243,11 @@ 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(INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 		}
 
 		/*
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..7c29b23b01a 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 set 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/parallel.c b/src/backend/access/transam/parallel.c
index 94db1ec3012..30cdce00b47 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -34,6 +34,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/spin.h"
@@ -759,16 +760,16 @@ 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.  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(INTERRUPT_GENERAL,
+								   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_GENERAL);
 			}
 		}
 
@@ -877,15 +878,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * or latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_GENERAL should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1038,7 +1040,7 @@ HandleParallelMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 799fc739e18..940554def1a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -74,6 +74,7 @@
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/startup.h"
 #include "postmaster/walsummarizer.h"
 #include "postmaster/walwriter.h"
@@ -86,7 +87,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"
@@ -2664,7 +2664,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 		ProcNumber	walwriterProc = procglobal->walwriterProc;
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->walwriterProc);
 	}
 }
 
@@ -9382,11 +9382,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 8c3090165f0..8f43ce62909 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -26,9 +26,9 @@
 #include "funcapi.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.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"
@@ -718,17 +718,17 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index a829a055a97..c244f6c5ec0 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -47,14 +47,15 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/startup.h"
 #include "replication/slot.h"
 #include "replication/slotsync.h"
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -316,23 +317,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.
 	 */
@@ -467,7 +451,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -541,13 +524,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).
@@ -1637,13 +1613,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);
 }
 
 /*
@@ -1803,7 +1772,7 @@ PerformWalRecovery(void)
 			}
 
 			/*
-			 * If we've been asked to lag the primary, wait on latch until
+			 * If we've been asked to lag the primary, wait on interrupt until
 			 * enough time has passed.
 			 */
 			if (recoveryApplyDelay(xlogreader))
@@ -3026,8 +2995,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)
@@ -3035,11 +3004,19 @@ recoveryApplyDelay(XLogReaderState *record)
 
 	while (true)
 	{
-		ResetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+		/*
+		 * INTERRUPT_GENERAL is used for all the usual interrupts, like config
+		 * reloads.  The wakeups when more WAL arrive use a different
+		 * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL
+		 * arriving don't wake up the startup process excessively when we're
+		 * waiting in other places, like for recovery conflicts.
+		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* This might change recovery_min_apply_delay. */
 		ProcessStartupProcInterrupts();
 
+		ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 		if (CheckForStandbyTrigger())
 			break;
 
@@ -3060,10 +3037,11 @@ 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(INTERRUPT_RECOVERY_CONTINUE |
+							 INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 msecs,
+							 WAIT_EVENT_RECOVERY_APPLY_DELAY);
 	}
 	return true;
 }
@@ -3718,15 +3696,17 @@ 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(INTERRUPT_RECOVERY_CONTINUE |
+											 INTERRUPT_GENERAL,
+											 WL_INTERRUPT | WL_TIMEOUT |
+											 WL_EXIT_ON_PM_DEATH,
+											 wait_time,
+											 WAIT_EVENT_RECOVERY_RETRIEVE_RETRY_INTERVAL);
+						ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 						now = GetCurrentTimestamp();
 
 						/* Handle interrupt signals of startup process */
+						ClearInterrupt(INTERRUPT_GENERAL);
 						ProcessStartupProcInterrupts();
 					}
 					last_fail_time = now;
@@ -3993,11 +3973,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(INTERRUPT_RECOVERY_CONTINUE |
+										 INTERRUPT_GENERAL,
+										 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+										 -1L,
+										 WAIT_EVENT_RECOVERY_WAL_STREAM);
+					ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
 					break;
 				}
 
@@ -4017,6 +3998,7 @@ WaitForWALToBecomeAvailable(XLogRecPtr RecPtr, bool randAccess,
 		 * This possibly-long loop needs to handle interrupts of startup
 		 * process.
 		 */
+		ClearInterrupt(INTERRUPT_GENERAL);
 		ProcessStartupProcInterrupts();
 	}
 
@@ -4491,7 +4473,10 @@ CheckPromoteSignal(void)
 void
 WakeupRecovery(void)
 {
-	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
+
+	if (procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_RECOVERY_CONTINUE, procno);
 }
 
 /*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index b2b743238f9..861043c6b4a 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -17,7 +17,7 @@
 #include "backup/basebackup_sink.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "postmaster/interrupt.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,21 +163,21 @@ throttle(bbsink_throttle *sink, size_t increment)
 		if (sleep <= 0)
 			break;
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
-		/* We're eating a potentially set latch, so check for interrupts */
+		/* We're eating a wakeup, so check for interrupts */
 		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be
 		 * the maximum time to sleep. Thus the cast to long is safe.
 		 */
-		wait_result = WaitLatch(MyLatch,
-								WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								(long) (sleep / 1000),
-								WAIT_EVENT_BASE_BACKUP_THROTTLE);
+		wait_result = WaitInterrupt(INTERRUPT_GENERAL,
+									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									(long) (sleep / 1000),
+									WAIT_EVENT_BASE_BACKUP_THROTTLE);
 
-		if (wait_result & WL_LATCH_SET)
+		if (wait_result & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
 
 		/* Done waiting? */
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 4bd37d5beb5..2b5b92abe38 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -91,7 +91,7 @@
  *	  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
+ *	  raises INTERRUPT_GENERAL, which triggers the event to be processed
  *	  immediately if this backend 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
@@ -140,6 +140,7 @@
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
+#include "postmaster/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
 #include "storage/procsignal.h"
@@ -406,9 +407,9 @@ static NotificationList *pendingNotifies = NULL;
 /*
  * 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.
+ * notifyInterruptPending flag and raises the INTERRUPT_GENERAL interrupt.
+ * ProcessNotifyInterrupt() will then be called whenever it's safe to actually
+ * deal with the interrupt.
  */
 volatile sig_atomic_t notifyInterruptPending = false;
 
@@ -1812,7 +1813,7 @@ HandleNotifyInterrupt(void)
 	notifyInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1821,10 +1822,10 @@ HandleNotifyInterrupt(void)
  *		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.
+ *		HandleNotifyInterrupt() will cause the read to be interrupted with
+ *		INTERRUPT_GENERAL, and this routine will get called.  If we are truly
+ *		idle (ie, *not* inside a transaction block), process the incoming
+ *		notifies.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index e81c9a8aba3..65b897476ce 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2496,8 +2496,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 15c4227cc62..df4b5fe1627 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,8 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1042,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;
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index dc7d1830259..11e5b3f083b 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -36,6 +36,7 @@
 #include "executor/tqueue.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
+#include "postmaster/interrupt.h"
 #include "utils/wait_event.h"
 
 
@@ -382,9 +383,10 @@ 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(INTERRUPT_GENERAL,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 81e2f8184e3..af282b3e802 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1653,8 +1653,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(hbaPort *port)
@@ -3088,8 +3089,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index a3d610b1373..07a4fb3f3d0 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -21,6 +21,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
+#include "postmaster/interrupt.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 
@@ -414,7 +415,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.
  *
@@ -450,9 +451,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
@@ -489,7 +489,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
@@ -668,9 +668,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 64ff3ce3d6a..782a0265978 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -31,8 +31,9 @@
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -522,8 +523,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 91576f94285..765ca0f3166 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -28,7 +28,7 @@
 #include <arpa/inet.h>
 
 #include "libpq/libpq.h"
-#include "miscadmin.h"
+#include "postmaster/interrupt.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -213,7 +213,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,12 +228,12 @@ 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,
@@ -241,9 +241,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientReadInterrupt(true);
 
 			/*
@@ -284,7 +284,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;
@@ -338,7 +339,7 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -350,9 +351,9 @@ retry:
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
 		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessClientWriteInterrupt(true);
 
 			/*
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index e5171467de1..f8465cb2d7b 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -76,6 +76,7 @@
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "port/pg_bswap.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
 #include "utils/guc_hooks.h"
@@ -175,7 +176,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(sizeof(Port));
@@ -287,8 +288,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
@@ -306,18 +307,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,
+									  INTERRUPT_GENERAL, 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;
 }
@@ -2063,7 +2064,7 @@ 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);
 
 retry:
 	rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0);
@@ -2071,15 +2072,15 @@ retry:
 	{
 		if (events[i].events & WL_SOCKET_CLOSED)
 			return false;
-		if (events[i].events & WL_LATCH_SET)
+		if (events[i].events & WL_INTERRUPT)
 		{
 			/*
-			 * A latch event might be preventing other events from being
+			 * An interrupt event might be preventing other events from being
 			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect latches to survive across
-			 * CHECK_FOR_INTERRUPTS().
+			 * because no code should expect INTERRUPT_GENERAL to survive
+			 * across CHECK_FOR_INTERRUPTS().
 			 */
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			goto retry;
 		}
 	}
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index f1a08bc32ca..b6928536984 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -19,6 +19,7 @@
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
@@ -181,9 +182,10 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_GENERAL);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 800815dfbcc..c45b332e822 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -90,7 +90,6 @@
 #include "postmaster/postmaster.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -575,24 +574,24 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
 		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * signal handlers will wake us by calling RaiseInterrupt).
 		 */
-		(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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessAutoVacLauncherInterrupts();
 
@@ -1359,7 +1358,7 @@ static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 4f6795f7265..61bcac8d9f8 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -23,6 +23,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 116ddf7b835..c82971e9274 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -18,11 +18,11 @@
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/bgworker_internals.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #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"
@@ -1226,9 +1226,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(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_STARTUP);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1236,7 +1236,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
@@ -1269,9 +1269,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(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH, 0,
+						   WAIT_EVENT_BGWORKER_SHUTDOWN);
 
 		if (rc & WL_POSTMASTER_DEATH)
 		{
@@ -1279,7 +1279,7 @@ WaitForBackgroundWorkerShutdown(BackgroundWorkerHandle *handle)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return status;
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index a688cc5d2a1..ae7d6f66cc6 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -224,7 +224,7 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 		int			rc;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessMainLoopInterrupts();
 
@@ -299,22 +299,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(INTERRUPT_GENERAL,
+						   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
-		 * 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.
+		 * If no interrupt event 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
+		 * 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
@@ -329,10 +329,10 @@ 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(INTERRUPT_GENERAL,
+								 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 0e228d143a0..363a42018e0 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -352,7 +352,7 @@ 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_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -534,7 +534,7 @@ 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)
@@ -572,10 +572,10 @@ 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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout * 1000L /* convert to ms */ ,
+							 WAIT_EVENT_CHECKPOINTER_MAIN);
 	}
 
 	/*
@@ -613,17 +613,17 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	for (;;)
 	{
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessCheckpointerInterrupts();
 
 		if (ShutdownRequestPending)
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
-						 0,
-						 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
+		(void) WaitInterrupt(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_CHECKPOINTER_SHUTDOWN);
 	}
 
 	/* Normal exit from the checkpointer is here */
@@ -804,10 +804,11 @@ 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(INTERRUPT_GENERAL,
+					  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT,
+					  100,
+					  WAIT_EVENT_CHECKPOINT_WRITE_DELAY);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 	else if (--absorb_counter <= 0)
 	{
@@ -916,7 +917,7 @@ static void
 ReqShutdownXLOG(SIGNAL_ARGS)
 {
 	ShutdownXLOGPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 
@@ -1033,14 +1034,14 @@ 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
+	 * Send INTERRUPT_GENERAL 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 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().
+	 * 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++)
@@ -1059,7 +1060,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1186,7 +1187,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, ProcGlobal->checkpointerProc);
 	}
 
 	return true;
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index 0ae9bf906ec..4cde7d52bd3 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -1,13 +1,13 @@
 /*-------------------------------------------------------------------------
  *
  * interrupt.c
- *	  Interrupt handling routines.
+ *	  Inter-process interrupts
  *
- * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
+ *	  src/backend/storage/ipc/interrupt.c
  *
  *-------------------------------------------------------------------------
  */
@@ -17,16 +17,264 @@
 #include <unistd.h>
 
 #include "miscadmin.h"
+#include "port/atomics.h"
 #include "postmaster/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
+#include "storage/waiteventset.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
 volatile sig_atomic_t ConfigReloadPending = false;
 volatile sig_atomic_t ShutdownRequestPending = false;
 
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint32 LocalPendingInterrupts;
+
+pg_atomic_uint32 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch backx.
+	 */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ */
+void
+RaiseInterrupt(uint32 interruptMask)
+{
+	uint32		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ */
+void
+SendInterrupt(uint32 interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint32		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(uint32 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(uint32 interruptMask, 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_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;
+}
+
 /*
  * Simple interrupt handler for main loops of background processes.
  */
@@ -61,7 +309,7 @@ void
 SignalHandlerForConfigReload(SIGNAL_ARGS)
 {
 	ConfigReloadPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -105,5 +353,5 @@ void
 SignalHandlerForShutdownRequest(SIGNAL_ARGS)
 {
 	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index dbe4e1d426b..4f28ea5b613 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -42,7 +42,6 @@
 #include "storage/condition_variable.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"
@@ -247,8 +246,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;
 
@@ -282,13 +281,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(&ProcGlobal->allProcs[arch_pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, arch_pgprocno);
 }
 
 
@@ -298,7 +296,7 @@ pgarch_waken_stop(SIGNAL_ARGS)
 {
 	/* set flag to do a final cycle and shut down afterwards */
 	ready_to_stop = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -318,7 +316,7 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
 		time_to_stop = ready_to_stop;
@@ -355,10 +353,10 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_GENERAL,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index d2a7a7add6f..82044abdb5d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -101,6 +101,7 @@
 #include "port/pg_bswap.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
@@ -549,7 +550,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
@@ -1620,14 +1620,14 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
-					  NULL);
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_GENERAL, NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1656,19 +1656,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_GENERAL);
 
 			/*
 			 * 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)
@@ -1961,7 +1962,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1971,7 +1972,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2048,7 +2049,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -2209,7 +2210,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 27e86cf393f..f2ac862513b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -28,6 +28,7 @@
 #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"
@@ -40,7 +41,7 @@
  * 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().
  * 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
@@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
 	/* Shutdown the recovery environment */
 	if (standbyState != STANDBY_DISABLED)
 		ShutdownRecoveryTransactionEnvironment();
+
+	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
 }
 
 
@@ -220,6 +223,12 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	MyBackendType = B_STARTUP;
 	AuxiliaryProcessMainCommon();
 
+	/*
+	 * Advertise our proc number so that backends can wake us up, when the
+	 * server is promoted or recovery is paused/resumed.
+	 */
+	ProcGlobal->startupProc = MyProcNumber;
+
 	/* Arrange to clean up at startup process exit */
 	on_shmem_exit(StartupProcExit, 0);
 
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 50c2edec1f6..c4c04ae86d4 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -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"
@@ -328,7 +327,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
@@ -338,9 +337,9 @@ 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_GENERAL, 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 */
@@ -356,7 +355,7 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 #endif
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
 		 * Process any requests or signals received recently.
@@ -1188,7 +1187,7 @@ pipeThread(void *arg)
 				 ftello(csvlogFile) >= Log_RotationSize * (pgoff_t) 1024) ||
 				(jsonlogFile != NULL &&
 				 ftello(jsonlogFile) >= Log_RotationSize * (pgoff_t) 1024))
-				SetLatch(MyLatch);
+				RaiseInterrupt(INTERRUPT_GENERAL);
 		}
 		LeaveCriticalSection(&sysloggerSection);
 	}
@@ -1199,8 +1198,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_GENERAL);
 
 	LeaveCriticalSection(&sysloggerSection);
 	_endthread();
@@ -1595,5 +1594,5 @@ static void
 sigUsr1Handler(SIGNAL_ARGS)
 {
 	rotation_requested = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index ccba0f84e6e..3381767149d 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -40,7 +40,6 @@
 #include "replication/walreceiver.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"
@@ -316,10 +315,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) */
@@ -631,8 +628,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)
@@ -647,7 +644,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&ProcGlobal->allProcs[pgprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, pgprocno);
 }
 
 /*
@@ -1641,11 +1638,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(INTERRUPT_GENERAL,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_GENERAL);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 0380601bcbb..ae097472200 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -222,12 +222,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))
 		{
@@ -236,7 +236,7 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		}
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* Process any signals received recently */
 		ProcessMainLoopInterrupts();
@@ -263,9 +263,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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 1b158c9d288..aaa12ccdbd0 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -29,8 +29,8 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "pqexpbuffer.h"
+#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
@@ -237,16 +237,16 @@ libpqrcv_connect(const char *conninfo, bool replication, bool logical,
 		else
 			io_flag = WL_SOCKET_WRITEABLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-							   PQsocket(conn->streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
+		rc = WaitInterruptOrSocket(INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+								   PQsocket(conn->streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_CONNECT);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
@@ -848,17 +848,17 @@ libpqrcv_PQgetResult(PGconn *streamConn)
 		 * since we'll get interrupted by signals and can handle any
 		 * interrupts here.
 		 */
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-							   WL_LATCH_SET,
-							   PQsocket(streamConn),
-							   0,
-							   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
+		rc = WaitInterruptOrSocket(INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+								   WL_INTERRUPT,
+								   PQsocket(streamConn),
+								   0,
+								   WAIT_EVENT_LIBPQWALRECEIVER_RECEIVE);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			ProcessWalRcvInterrupts();
 		}
 
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index d25085d3515..176deb406c6 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -804,13 +804,13 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 				int			rc;
 
 				/* Wait for more work. */
-				rc = WaitLatch(MyLatch,
-							   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   1000L,
-							   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
+				rc = WaitInterrupt(INTERRUPT_GENERAL,
+								   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   1000L,
+								   WAIT_EVENT_LOGICAL_PARALLEL_APPLY_MAIN);
 
-				if (rc & WL_LATCH_SET)
-					ResetLatch(MyLatch);
+				if (rc & WL_INTERRUPT)
+					ClearInterrupt(INTERRUPT_GENERAL);
 			}
 		}
 		else
@@ -990,7 +990,7 @@ HandleParallelApplyMessageInterrupt(void)
 {
 	InterruptPending = true;
 	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1182,14 +1182,14 @@ 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(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   SHM_SEND_RETRY_INTERVAL_MS,
+						   WAIT_EVENT_LOGICAL_APPLY_SEND_DATA);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1254,13 +1254,13 @@ pa_wait_for_xact_state(ParallelApplyWorkerInfo *winfo,
 			break;
 
 		/* Wait to be signalled. */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 10L,
-						 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
+		(void) WaitInterrupt(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 10L,
+							 WAIT_EVENT_LOGICAL_PARALLEL_APPLY_STATE_CHANGE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* 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 a3c7adbf1a8..071d93ce816 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -209,16 +209,17 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
@@ -544,13 +545,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -585,13 +586,13 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -667,7 +668,7 @@ logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
 }
 
 /*
- * Wake up (using latch) any logical replication worker for specified sub/rel.
+ * Wake up (using interrupt) any logical replication worker for specified sub/rel.
  */
 void
 logicalrep_worker_wakeup(Oid subid, Oid relid)
@@ -685,7 +686,7 @@ logicalrep_worker_wakeup(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.
  */
@@ -694,7 +695,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -1212,14 +1213,14 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   wait_time,
-					   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   wait_time,
+						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 2c0a7439be4..09b2d0d181e 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1252,13 +1252,13 @@ 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);
+	rc = WaitInterrupt(INTERRUPT_GENERAL,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1588,13 +1588,13 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 6af5c9fe16c..dc9077adb75 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -104,6 +104,7 @@
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalrelation.h"
 #include "replication/logicalworker.h"
@@ -210,11 +211,11 @@ wait_for_relation_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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 1000L, WAIT_EVENT_LOGICAL_SYNC_STATE_CHANGE);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return false;
@@ -260,15 +261,15 @@ 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(INTERRUPT_GENERAL,
+						   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_GENERAL);
 	}
 
 	return false;
@@ -555,7 +556,7 @@ process_syncing_tables_for_apply(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
 						 */
 						CommitTransactionCommand();
 						pgstat_report_stat(false);
@@ -771,14 +772,14 @@ 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);
+		(void) WaitInterruptOrSocket(INTERRUPT_GENERAL,
+									 WL_SOCKET_READABLE | WL_INTERRUPT |
+									 WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									 fd, 1000L, WAIT_EVENT_LOGICAL_SYNC_DATA);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	return bytesread;
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 31ab69ea13a..bb566c1ee19 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -3742,26 +3742,26 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
 		else
 			wait_time = NAPTIME_PER_CYCLE;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(INTERRUPT_GENERAL,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index d75e3968035..93d8b9d0605 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -78,6 +78,7 @@
 #include "common/int.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "replication/walsender.h"
 #include "replication/walsender_private.h"
@@ -222,22 +223,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -282,11 +283,13 @@ 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 interrupt, so no need for timeout.
 		 */
-		rc = WaitLatch(MyLatch, WL_LATCH_SET | WL_POSTMASTER_DEATH, -1,
-					   WAIT_EVENT_SYNC_REP);
+		rc = WaitInterrupt(INTERRUPT_GENERAL,
+						   WL_INTERRUPT | WL_POSTMASTER_DEATH,
+						   -1,
+						   WAIT_EVENT_SYNC_REP);
 
 		/*
 		 * If the postmaster dies, we'll probably never get an acknowledgment,
@@ -902,7 +905,7 @@ SyncRepWakeQueue(bool all, int mode)
 		/*
 		 * Wake only when we have set state and removed from queue.
 		 */
-		SetLatch(&(proc->procLatch));
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 		numprocs++;
 	}
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 2e5dd6deb2c..87b1bad279f 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -146,17 +146,17 @@ static void ProcessWalSndrMessage(XLogRecPtr walEnd, TimestampTz sendTime);
 static void WalRcvComputeNextWakeup(WalRcvWakeupReason reason, TimestampTz now);
 
 /*
- * Process any interrupts the walreceiver process may have received.
- * This should be called any time the process's latch has become set.
+ * Process any interrupts the walreceiver process may have received.  This
+ * should be called any time the INTERRUPT_GENERAL interrupt has become set.
  *
  * Currently, only SIGTERM is of interest.  We can't just exit(1) within the
  * SIGTERM signal handler, because the signal might arrive in the middle of
  * some critical operation, like while we're holding a spinlock.  Instead, the
- * signal handler sets a flag variable as well as setting the process's latch.
+ * signal handler sets a flag variable as well as raising INTERRUPT_GENERAL.
  * We must check the flag (by calling ProcessWalRcvInterrupts) anytime the
- * latch has become set.  Operations that could block for a long time, such as
- * reading from a remote server, must pay attention to the latch too; see
- * libpqrcv_PQgetResult for example.
+ * INTERRUPT_GENERAL has become set.  Operations that could block for a long
+ * time, such as reading from a remote server, must pay attention to the
+ * interrupt too; see libpqrcv_PQgetResult for example.
  */
 void
 ProcessWalRcvInterrupts(void)
@@ -536,25 +536,25 @@ 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(INTERRUPT_GENERAL,
+										   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_GENERAL);
 					ProcessWalRcvInterrupts();
 
 					if (walrcv->force_reply)
@@ -702,7 +702,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		ProcessWalRcvInterrupts();
 
@@ -734,8 +734,10 @@ 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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
 	if (update_process_title)
@@ -1388,7 +1390,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 8de2886ff0b..48e25fb7dab 100644
--- a/src/backend/replication/walreceiverfuncs.c
+++ b/src/backend/replication/walreceiverfuncs.c
@@ -25,6 +25,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -317,7 +318,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_GENERAL, walrcv_proc);
 }
 
 /*
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 446d10c1a7d..0afa181550b 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1013,8 +1013,8 @@ 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
+ * Inside the walsender we can do better than read_local_xlog_page, which
+ * has to do a plain sleep/busy loop, because the walsender's interrupt gets
  * set every time WAL is flushed.
  */
 static int
@@ -1611,7 +1611,7 @@ ProcessPendingWrites(void)
 				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1628,8 +1628,8 @@ ProcessPendingWrites(void)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
@@ -1817,7 +1817,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		long		sleeptime;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -1937,8 +1937,8 @@ WalSndWaitForWal(XLogRecPtr loc)
 		WalSndWait(wakeEvents, sleeptime, wait_event);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	return RecentFlushPtr;
 }
 
@@ -2754,7 +2754,7 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	for (;;)
 	{
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -3553,7 +3553,7 @@ static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
 	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* Set up signal handlers */
@@ -3659,7 +3659,7 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
@@ -3671,8 +3671,8 @@ WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
 	 * 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().
+	 * 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
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7915ed624c1..d7be4309d4e 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -48,6 +48,7 @@
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/fd.h"
@@ -2880,7 +2881,7 @@ UnpinBufferNoOwner(BufferDesc *buf)
 
 				buf_state &= ~BM_PIN_COUNT_WAITER;
 				UnlockBufHdr(buf, buf_state);
-				ProcSendSignal(wait_backend_pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, wait_backend_pgprocno);
 			}
 			else
 				UnlockBufHdr(buf, buf_state);
@@ -5283,7 +5284,12 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+		{
+			WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_PIN);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index 336715b6c63..19389770386 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -17,6 +17,7 @@
 
 #include "pgstat.h"
 #include "port/atomics.h"
+#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
 #include "storage/proc.h"
@@ -219,27 +220,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *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(&ProcGlobal->allProcs[bgwprocno].procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, bgwprocno);
 	}
 
 	/*
@@ -420,10 +419,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index c6aefd2f688..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,387 +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-2025, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-	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 b1b73dac3be..3215830d41b 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/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 7d201965503..ddbb041ff9f 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -22,11 +22,11 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
+#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -483,7 +483,7 @@ HandleProcSignalBarrierInterrupt(void)
 {
 	InterruptPending = true;
 	ProcSignalBarrierPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* interrupt will be raised by procsignal_sigusr1_handler */
 }
 
 /*
@@ -714,7 +714,7 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
 		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
 
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 2c79a649f46..0a9879d7024 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.
  *
@@ -22,6 +22,7 @@
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
 #include "storage/shm_mq.h"
 #include "storage/spin.h"
 #include "utils/memutils.h"
@@ -44,10 +45,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 SendIntterupt/ClearInterrupt, do we need to add some?
  *
  * mq_ring_size and mq_ring_offset never change after initialization, and
  * can therefore be read without the lock.
@@ -112,7 +114,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
@@ -214,7 +216,7 @@ shm_mq_set_receiver(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (sender != NULL)
-		SetLatch(&sender->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
@@ -232,7 +234,7 @@ shm_mq_set_sender(shm_mq *mq, PGPROC *proc)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (receiver != NULL)
-		SetLatch(&receiver->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(receiver));
 }
 
 /*
@@ -341,14 +343,14 @@ 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 on our process interrupt when the ring
+ * buffer fills up, and then continue writing once the receiver has drained
+ * some data.  The process interrupt is cleared after each wait.
  *
- * When nowait = true, we do not manipulate the state of the process latch;
+ * When nowait = true, we do not manipulate the state of the process interrupt;
  * instead, if the buffer becomes full, we return SHM_MQ_WOULD_BLOCK.  In
  * this case, the caller should call this function again, with the same
- * arguments, each time the process latch is set.  (Once begun, the sending
+ * arguments, each time the process interrupt is set.  (Once begun, the sending
  * of a message cannot be aborted except by detaching from the queue; changing
  * the length or payload will corrupt the queue.)
  *
@@ -539,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_GENERAL, GetNumberFromPGProc(receiver));
 		mqh->mqh_send_pending = 0;
 	}
 
@@ -557,16 +559,16 @@ 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
+ * When nowait = false, we'll wait on our process interrupt when the ring
+ * buffer is empty and we have not yet received a full message.  The sender
+ * will set our process interrupt after more data has been written, and we'll
+ * resume processing.  Each call will therefore return a complete message
  * (unless the sender detaches the queue).
  *
- * When nowait = 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 manipulate the state of the process
+ * interrupt; instead, whenever the buffer is empty and we need to read from
+ * it, we return SHM_MQ_WOULD_BLOCK.  In this case, the caller should call
+ * this function again after the process interrupt has been set.
  */
 shm_mq_result
 shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
@@ -619,8 +621,8 @@ shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bool nowait)
 	 * If we've consumed an amount of data greater than 1/4th of the ring
 	 * size, mark it consumed in shared memory.  We try to avoid doing this
 	 * unnecessarily when only a small amount of data has been consumed,
-	 * because SetLatch() is fairly expensive and we don't want to do it too
-	 * often.
+	 * because SendInterrupt() is fairly expensive and we don't want to do it
+	 * too often.
 	 */
 	if (mqh->mqh_consume_pending > mq->mq_ring_size / 4)
 	{
@@ -895,7 +897,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +995,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 * Therefore, we can read it without acquiring the spinlock.
 			 */
 			Assert(mqh->mqh_counterparty_attached);
-			SetLatch(&mq->mq_receiver->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(mq->mq_receiver));
 
 			/*
 			 * We have just updated the mqh_send_pending bytes in the shared
@@ -1001,7 +1003,7 @@ shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const void *data,
 			 */
 			mqh->mqh_send_pending = 0;
 
-			/* Skip manipulation of our latch if nowait = true. */
+			/* Skip waiting if nowait = true. */
 			if (nowait)
 			{
 				*bytes_written = sent;
@@ -1009,17 +1011,17 @@ 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(INTERRUPT_GENERAL, 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_GENERAL);
 
 			/* An interrupt may have occurred while we were waiting. */
 			CHECK_FOR_INTERRUPTS();
@@ -1054,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.
 			 */
@@ -1150,22 +1152,22 @@ 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_RECEIVE);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1250,11 +1252,11 @@ 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_INTERNAL);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
@@ -1293,7 +1295,7 @@ shm_mq_inc_bytes_read(shm_mq *mq, Size n)
 	 */
 	sender = mq->mq_sender;
 	Assert(sender != NULL);
-	SetLatch(&sender->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(sender));
 }
 
 /*
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index bebc97ecffd..ab22e0b981d 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_authid.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/syslogger.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -204,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 6eea8e87169..4a66e1b7f7c 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -16,7 +16,7 @@
 
 #include "access/xact.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "postmaster/interrupt.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
@@ -31,8 +31,8 @@ uint64		SharedInvalidMessageCounter;
  * 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
+ * The signal handler will set an interrupt pending flag and raise the
+ * INTERRUPT_GENERAL. Whenever starting to read from the client, or when
  * interrupted while doing so, ProcessClientReadInterrupt() will call
  * ProcessCatchupEvent().
  */
@@ -148,7 +148,7 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
  *
  * 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.
+ * raising INTERRUPT_GENERAL.
  */
 void
 HandleCatchupInterrupt(void)
@@ -161,7 +161,7 @@ HandleCatchupInterrupt(void)
 	catchupInterruptPending = true;
 
 	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 5acb4508f85..1ef7600522c 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -24,6 +24,7 @@
 #include "access/xlogutils.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "replication/slot.h"
 #include "storage/bufmgr.h"
 #include "storage/proc.h"
@@ -594,7 +595,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
@@ -696,7 +697,10 @@ 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  PG_WAIT_LOCK | locktag.locktag_type);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	/*
 	 * Exit if ltime is reached. Then all the backends holding conflicting
@@ -745,7 +749,10 @@ 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+					  PG_WAIT_LOCK | locktag.locktag_type);
+		ClearInterrupt(INTERRUPT_GENERAL);
+		CHECK_FOR_INTERRUPTS();
 	}
 
 cleanup:
@@ -837,9 +844,14 @@ 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
+	 * the signal handler??
 	 */
-	ProcWaitForSignal(WAIT_EVENT_BUFFER_PIN);
+	WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+				  WAIT_EVENT_BUFFER_PIN);
+	ClearInterrupt(INTERRUPT_GENERAL);
+	CHECK_FOR_INTERRUPTS();
 
 	if (got_standby_delay_timeout)
 		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 7c0e66900f9..1eba76a19c4 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
@@ -68,11 +69,12 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #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"
@@ -127,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 doing syscalls related to waiting.
 	 */
-	Latch	   *latch;
-	int			latch_pos;
+	uint32		interrupt_mask;
+	int			interrupt_pos;
 
 	/*
 	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
@@ -166,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -182,9 +184,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
 
@@ -234,7 +243,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)
@@ -284,12 +293,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");
@@ -310,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -348,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -411,7 +426,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;
 
@@ -496,9 +512,9 @@ FreeWaitEventSet(WaitEventSet *set)
 		 cur_event < (set->events + set->nevents);
 		 cur_event++)
 	{
-		if (cur_event->events & WL_LATCH_SET)
+		if (cur_event->events & WL_INTERRUPT)
 		{
-			/* uses the latch's HANDLE */
+			/* uses the process's wakeup HANDLE */
 		}
 		else if (cur_event->events & WL_POSTMASTER_DEATH)
 		{
@@ -535,7 +551,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
 
 /* ---
  * Add an event to the set. Possible events are:
- * - WL_LATCH_SET: Wait for the latch to be set
+ * - WL_INTERRUPT: Wait for the interrupt to be set
  * - 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
@@ -553,10 +569,6 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * Returns the offset in WaitEventSet->events (starting from 0), which can be
  * used to modify previously added wait events using ModifyWaitEvent().
  *
- * In the WL_LATCH_SET case the latch must be owned by the current process,
- * i.e. it must be a process-local latch initialized with InitLatch, or a
- * shared latch associated with the current process by calling OwnLatch.
- *
  * In the WL_SOCKET_READABLE/WRITEABLE/CONNECTED/ACCEPT cases, EOF and error
  * conditions cause the socket to be reported as readable/writable/connected,
  * so that the caller can deal with the condition.
@@ -566,7 +578,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, uint32 interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +592,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 */
@@ -608,10 +617,10 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 	event->reset = false;
 #endif
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		set->latch = latch;
-		set->latch_pos = event->pos;
+		set->interrupt_mask = interruptMask;
+		set->interrupt_pos = event->pos;
 #if defined(WAIT_USE_SELF_PIPE)
 		event->fd = selfpipe_readfd;
 #elif defined(WAIT_USE_SIGNALFD)
@@ -645,14 +654,14 @@ AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
 }
 
 /*
- * Change the event mask and, in the WL_LATCH_SET case, the latch associated
- * with the WaitEvent.  The latch may be changed to NULL to disable the latch
- * temporarily, and then set back to a latch later.
+ * Change the event mask and, in the WL_INTERRUPT case, the interrupt mask
+ * associated with the WaitEvent.  The interrupt mask may be changed to 0 to
+ * disable the wakeups on interrupts temporarily, and then set back later.
  *
  * 'pos' is the id returned by AddWaitEventToSet.
  */
 void
-ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
+ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, uint32 interruptMask)
 {
 	WaitEvent  *event;
 #if defined(WAIT_USE_KQUEUE)
@@ -688,34 +697,28 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +748,8 @@ WaitEventAdjustEpoll(WaitEventSet *set, WaitEvent *event, int action)
 	epoll_ev.events = EPOLLERR | EPOLLHUP;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		epoll_ev.events |= EPOLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -794,9 +796,8 @@ WaitEventAdjustPoll(WaitEventSet *set, WaitEvent *event)
 	pollfd->fd = event->fd;
 
 	/* prepare pollfd entry once */
-	if (event->events == WL_LATCH_SET)
+	if (event->events == WL_INTERRUPT)
 	{
-		Assert(set->latch != NULL);
 		pollfd->events = POLLIN;
 	}
 	else if (event->events == WL_POSTMASTER_DEATH)
@@ -858,9 +859,9 @@ WaitEventAdjustKqueueAddPostmaster(struct kevent *k_ev, WaitEvent *event)
 }
 
 static inline void
-WaitEventAdjustKqueueAddLatch(struct kevent *k_ev, WaitEvent *event)
+WaitEventAdjustKqueueAddInterruptWakeup(struct kevent *k_ev, WaitEvent *event)
 {
-	/* For now latch can only be added, not removed. */
+	/* For now interrupt wakeup can only be added, not removed. */
 	k_ev->ident = SIGURG;
 	k_ev->filter = EVFILT_SIGNAL;
 	k_ev->flags = EV_ADD;
@@ -886,8 +887,7 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 	if (old_events == event->events)
 		return;
 
-	Assert(event->events != WL_LATCH_SET || set->latch != NULL);
-	Assert(event->events == WL_LATCH_SET ||
+	Assert(event->events == WL_INTERRUPT ||
 		   event->events == WL_POSTMASTER_DEATH ||
 		   (event->events & (WL_SOCKET_READABLE |
 							 WL_SOCKET_WRITEABLE |
@@ -902,10 +902,10 @@ WaitEventAdjustKqueue(WaitEventSet *set, WaitEvent *event, int old_events)
 		 */
 		WaitEventAdjustKqueueAddPostmaster(&k_ev[count++], event);
 	}
-	else if (event->events == WL_LATCH_SET)
+	else if (event->events == WL_INTERRUPT)
 	{
-		/* We detect latch wakeup using a signal event. */
-		WaitEventAdjustKqueueAddLatch(&k_ev[count++], event);
+		/* We detect interrupt wakeup using a signal event. */
+		WaitEventAdjustKqueueAddInterruptWakeup(&k_ev[count++], event);
 	}
 	else
 	{
@@ -983,10 +983,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)
 	{
@@ -1042,6 +1045,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,64 +1067,57 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		uint32		old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, 1 << SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
 			 * zero timeout to see what non-latch events we can fit into the
@@ -1129,34 +1126,58 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 			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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) 1 << SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1227,16 +1248,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1389,13 +1410,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1511,16 +1532,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1619,6 +1640,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
@@ -1724,19 +1754,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1781,7 +1807,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
@@ -1887,18 +1913,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)
 {
@@ -1915,7 +1940,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;
@@ -2002,14 +2027,13 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
  * NB: 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.) XXX
-  *
+ *
  * NB: this function is called from critical sections and signal handlers so
  * throwing an error is not a good idea.
  *
@@ -2018,6 +2042,7 @@ ResOwnerReleaseWaitEventSet(Datum res)
 void
 WakeupMyProc(void)
 {
+#ifndef WIN32
 #if defined(WAIT_USE_SELF_PIPE)
 	if (waiting)
 		sendSelfPipeByte();
@@ -2025,12 +2050,23 @@ 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);
-}
+#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 228303e77f7..295914cf032 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include "miscadmin.h"
 #include "portability/instr_time.h"
+#include "postmaster/interrupt.h"
 #include "storage/condition_variable.h"
 #include "storage/proc.h"
 #include "storage/proclist.h"
@@ -147,23 +148,23 @@ 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(INTERRUPT_GENERAL, 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_GENERAL);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -176,9 +177,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))
@@ -266,9 +268,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_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -297,8 +299,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
@@ -331,7 +333,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -355,6 +357,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index 5b21a053981..ab9b46b9cb9 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -207,6 +207,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
+#include "postmaster/interrupt.h"
 #include "storage/predicate.h"
 #include "storage/predicate_internals.h"
 #include "storage/proc.h"
@@ -1576,7 +1577,10 @@ GetSafeSnapshot(Snapshot origSnapshot)
 				 SxactIsROUnsafe(MySerializableXact)))
 		{
 			LWLockRelease(SerializableXactHashLock);
-			ProcWaitForSignal(WAIT_EVENT_SAFE_SNAPSHOT);
+			WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_SAFE_SNAPSHOT);
+			ClearInterrupt(INTERRUPT_GENERAL);
+			CHECK_FOR_INTERRUPTS();
 			LWLockAcquire(SerializableXactHashLock, LW_EXCLUSIVE);
 		}
 		MySerializableXact->flags &= ~SXACT_FLAG_DEFERRABLE_WAITING;
@@ -3609,7 +3613,7 @@ ReleasePredicateLocks(bool isCommit, bool isReadOnlySafe)
 			 */
 			if (SxactIsDeferrableWaiting(roXact) &&
 				(SxactIsROUnsafe(roXact) || SxactIsROSafe(roXact)))
-				ProcSendSignal(roXact->pgprocno);
+				SendInterrupt(INTERRUPT_GENERAL, roXact->pgprocno);
 		}
 	}
 
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 749a79d48ef..4ebff9a4b37 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -39,6 +39,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
+#include "postmaster/interrupt.h"
 #include "replication/slotsync.h"
 #include "replication/syncrep.h"
 #include "storage/condition_variable.h"
@@ -258,14 +259,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -479,13 +494,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);
@@ -649,13 +659,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -932,21 +937,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1009,13 +1013,12 @@ AuxiliaryProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/* look at the equivalent ProcKill() code for comments */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	SpinLockAcquire(ProcStructLock);
 
@@ -1342,18 +1345,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
 	{
@@ -1399,9 +1402,9 @@ 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 PG_WAIT_LOCK | locallock->tag.lock.locktag_type);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			/* check for deadlocks first, as that's probably log-worthy */
 			if (got_deadlock_timeout)
 			{
@@ -1693,7 +1696,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 links invalid.
  *
@@ -1722,7 +1725,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1874,45 +1877,19 @@ 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.
+	 * 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.
 	 *
 	 * Note that, when this function runs inside procsignal_sigusr1_handler(),
-	 * the handler function sets the latch again after the latch is set here.
+	 * the handler function raises the interrupt again after the interrupt is
+	 * raised here.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 	errno = save_errno;
 }
 
-/*
- * 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(&ProcGlobal->allProcs[procNumber].procLatch);
-}
-
 /*
  * BecomeLockGroupLeader - designate process as lock group leader
  *
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index fc16db90133..e61df4675c9 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -26,8 +26,8 @@
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/interrupt.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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/postgres.c b/src/backend/tcop/postgres.c
index 947ffb40421..8784ea78dcd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -519,15 +519,15 @@ ProcessClientReadInterrupt(bool blocked)
 		/*
 		 * 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
+		 * make sure the interrupt flag is set, so that if there is no data
 		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while reading.
 		 */
 		if (blocked)
 			CHECK_FOR_INTERRUPTS();
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -553,9 +553,9 @@ ProcessClientWriteInterrupt(bool blocked)
 		 * 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
+		 * sure the interrupt flag is set, so that if the write would block
 		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
+		 * make sure the interrupt flag is set, as we might've undesirably
 		 * cleared it while writing.
 		 */
 		if (blocked)
@@ -579,7 +579,7 @@ ProcessClientWriteInterrupt(bool blocked)
 			}
 		}
 		else
-			SetLatch(MyLatch);
+			RaiseInterrupt(INTERRUPT_GENERAL);
 	}
 
 	errno = save_errno;
@@ -3012,12 +3012,12 @@ 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);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on 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.
 	 */
@@ -3041,8 +3041,8 @@ StatementCancelHandler(SIGNAL_ARGS)
 		QueryCancelPending = true;
 	}
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* If we're still here, waken anything waiting on the interrupt */
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /* signal handler for floating point exception */
@@ -3068,7 +3068,7 @@ HandleRecoveryConflictInterrupt(ProcSignalReason reason)
 	RecoveryConflictPendingReasons[reason] = true;
 	RecoveryConflictPending = true;
 	InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 6fcfd031428..d7e9da57bd1 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -35,10 +35,10 @@
 #include "parser/parse_type.h"
 #include "parser/scansup.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #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"
@@ -373,16 +373,16 @@ pg_sleep(PG_FUNCTION_ARGS)
 	float8		endtime;
 
 	/*
-	 * 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.
 	 */
 #define GetNowFloat()	((float8) GetCurrentTimestamp() / 1000000.0)
 
@@ -403,11 +403,11 @@ 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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 delay_ms,
+							 WAIT_EVENT_PG_SLEEP);
+		ClearInterrupt(INTERRUPT_GENERAL);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 9682f9dbdca..e4d145b0529 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1738,10 +1738,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/init/globals.c b/src/backend/utils/init/globals.c
index b844f9fdaef..79bc999cf26 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -52,15 +52,6 @@ bool		MyCancelKeyValid = false;
 int32		MyCancelKey = 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 dc3521457c7..81664fc11ff 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"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -187,10 +183,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.
@@ -211,48 +206,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 ee1a9d5d98b..e11101a36b0 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -39,6 +39,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
+#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slot.h"
 #include "replication/slotsync.h"
@@ -1383,7 +1384,7 @@ TransactionTimeoutHandler(void)
 {
 	TransactionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1391,7 +1392,7 @@ IdleInTransactionSessionTimeoutHandler(void)
 {
 	IdleInTransactionSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1399,7 +1400,7 @@ IdleSessionTimeoutHandler(void)
 {
 	IdleSessionTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1407,7 +1408,7 @@ IdleStatsUpdateTimeoutHandler(void)
 {
 	IdleStatsUpdateTimeoutPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 static void
@@ -1415,7 +1416,7 @@ ClientCheckTimeoutHandler(void)
 {
 	CheckClientConnectionPending = true;
 	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index d92efa12550..e90d82c7ddc 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,7 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
+#include "postmaster/interrupt.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -371,10 +371,10 @@ handle_sig_alarm(SIGNAL_ARGS)
 	HOLD_INTERRUPTS();
 
 	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
+	 * SIGALRM is always cause for waking anything waiting on
+	 * INTERRUPT_GENERAL.
 	 */
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_GENERAL);
 
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 91060de0ab7..5233a65aaa0 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1273,7 +1273,7 @@ HandleLogMemoryContextInterrupt(void)
 {
 	InterruptPending = true;
 	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* INTERRUPT_GENERAL will be raised by procsignal_sigusr1_handler */
 }
 
 /*
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index f37be6d5690..a257d0a8ee6 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -14,6 +14,8 @@
 #ifndef PARALLEL_H
 #define PARALLEL_H
 
+#include <signal.h>
+
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 16205b824fa..dd361be4afd 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -42,8 +42,8 @@
 
 #include "libpq-fe.h"
 #include "miscadmin.h"
+#include "postmaster/interrupt.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -177,8 +177,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -209,16 +209,16 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(INTERRUPT_GENERAL,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
+			if (rc & WL_INTERRUPT)
 			{
-				ResetLatch(MyLatch);
+				ClearInterrupt(INTERRUPT_GENERAL);
 				CHECK_FOR_INTERRUPTS();
 			}
 
@@ -341,17 +341,17 @@ 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(INTERRUPT_GENERAL,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -407,7 +407,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)
@@ -436,10 +436,11 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
+			WaitInterruptOrSocket(INTERRUPT_GENERAL, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_GENERAL);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index aeb66ca40cf..6de03ce1163 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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/miscadmin.h b/src/include/miscadmin.h
index a2b63495eec..f5b773b79e5 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -190,7 +190,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT bool MyCancelKeyValid;
 extern PGDLLIMPORT int32 MyCancelKey;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -323,9 +322,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/interrupt.h b/src/include/postmaster/interrupt.h
index 3bf49320846..346f9cae8e1 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/postmaster/interrupt.h
@@ -1,13 +1,68 @@
 /*-------------------------------------------------------------------------
  *
  * interrupt.h
- *	  Interrupt handling routines.
+ *	  Inter-process interrupts
  *
- * Responses to interrupts are fairly varied and many types of backends
- * have their own implementations, but we provide a few generic things
- * here to facilitate code reuse.
+ * "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.
  *
- * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Interrupt flags can be "raised" synchronously by code that wants to defer
+ * an action, or asynchronously by timer signal handlers, other signal
+ * handlers or "sent" by other backends setting them directly.
+ *
+ * Most code currently deals with the INTERRUPT_GENERAL interrupt. It is
+ * raised by any of the events checked by CHECK_FOR_INTERRUPTS(), like query
+ * cancellation or idle session timeout. Well behaved backend code performs
+ * CHECK_FOR_INTERRUPTS() periodically in long computations, and should never
+ * sleep using mechanisms other than the WaitEventSet mechanism or the more
+ * convenient WaitInterrupt/WaitSockerOrInterrupt functions (except for
+ * bounded short periods, eg LWLock waits), so they should react in good time.
+ *
+ * The "standard" set of interrupts is handled by CHECK_FOR_INTERRUPTS(), and
+ * consists of tasks that are safe to perform at most times.  They can be
+ * suppressed by HOLD_INTERRUPTS()/RESUME_INTERRUPTS().
+ *
+ *
+ * The correct pattern to wait for event(s) using INTERRUPT_GENERAL is:
+ *
+ * for (;;)
+ * {
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ *	   if (work to do)
+ *		   Do Stuff();
+ *	   WaitInterrupt(INTERRUPT_GENERAL, ...);
+ * }
+ *
+ * It's important to clear the interrupt *before* checking if there's work to
+ * do. Otherwise, if someone sets the interrupt between the check and the
+ * ClearInterrupt() 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
+ *	   WaitInterrupt(INTERRUPT_GENERAL, ...);
+ *	   ClearInterrupt(INTERRUPT_GENERAL);
+ * }
+ *
+ * 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.
+ *
+ * To wake up the waiter, you must first set a global flag or something else
+ * that the wait loop tests in the "if (work to do)" part, and call
+ * SendInterrupt(INTERRUPT_GENERAL) *after* that. SendInterrupt is designed to
+ * return quickly if the interrupt is already set. In more complex scenarios
+ * with nested loops that can consume different events, you can define your
+ * own INTERRUPT_* flag instead of relying on INTERRUPT_GENERAL.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
@@ -19,11 +74,97 @@
 #ifndef INTERRUPT_H
 #define INTERRUPT_H
 
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/waiteventset.h"
+
 #include <signal.h>
 
+extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
+
 extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
 extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
 
+/*
+ * Flags in the pending interrupts bitmask. Each value represents one bit in
+ * the bitmask.
+ */
+typedef enum
+{
+	/*
+	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+	 * waiting for an interrupt. If it's set, the backend needs to be woken up
+	 * when a bit in the pending interrupts mask is set. It's used internally
+	 * by the interrupt machinery, and cannot be used directly in the public
+	 * functions. It's named differently to distinguish it from the actual
+	 * interrupt flags.
+	 */
+	SLEEPING_ON_INTERRUPTS = 1 << 0,
+
+	/*
+	 * INTERRUPT_GENERAL is multiplexed for many reasons, like query
+	 * cancellation termination requests, recovery conflicts, and config
+	 * reload requests.  Upon receiving INTERRUPT_GENERAL, you should call
+	 * CHECK_FOR_INTERRUPTS() to process those requests.  It is also used for
+	 * various other context-dependent purposes, but note that if it's used to
+	 * wake up for other reasons, you must still call CHECK_FOR_INTERRUPTS()
+	 * once per iteration.
+	 */
+	INTERRUPT_GENERAL = 1 << 1,
+
+	/*
+	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * it that it should continue WAL replay. It's sent by WAL receiver when
+	 * more WAL arrives, or when promotion is requested.
+	 */
+	INTERRUPT_RECOVERY_CONTINUE = 1 << 2,
+} InterruptType;
+
+/*
+ * Test an interrupt flag (of flags).
+ */
+static inline bool
+IsInterruptPending(uint32 interruptMask)
+{
+	return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag.
+ */
+static inline void
+ClearInterrupt(uint32 interruptMask)
+{
+	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag.
+ */
+static inline bool
+ConsumeInterrupt(uint32 interruptMask)
+{
+	if (likely(!IsInterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+
+	return true;
+}
+
+extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
+extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
+
+extern void RaiseInterrupt(uint32 interruptMask);
+extern void SendInterrupt(uint32 interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(uint32 interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
 extern void ProcessMainLoopInterrupts(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index e41dc70785a..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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-2025, 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 */
-
-/*
- * 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 114eb1f8f76..3a9221cfbc5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -17,7 +17,6 @@
 #include "access/clog.h"
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -173,9 +172,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -311,6 +307,13 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	pg_atomic_uint32 pendingInterrupts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -413,6 +416,8 @@ typedef struct PROC_HDR
 	 */
 	ProcNumber	walwriterProc;
 	ProcNumber	checkpointerProc;
+	ProcNumber	walreceiverProc;
+	ProcNumber	startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -490,9 +495,6 @@ extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
 extern void CheckDeadLockAlert(void);
 extern void LockErrorCleanup(void);
 
-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/waiteventset.h b/src/include/storage/waiteventset.h
index aa65b7a35e7..60ececb785c 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,18 +3,17 @@
  * 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.
  *
- * 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
+ * storage/ipc/interrupt.c for detailed specifications for the exported
  * functions.
  *
- *
  * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
@@ -25,13 +24,16 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
+#include <signal.h>
+
+#include "storage/procnumber.h"
 #include "utils/resowner.h"
 
 /*
- * 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() */
@@ -67,32 +69,35 @@ typedef struct WaitEvent
 #endif
 } WaitEvent;
 
-/* forward declarations to avoid exposing waiteventset.c implementation details */
+/* forward declaration to avoid exposing interrupt.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
 
 extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
 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);
-extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							  uint32 interruptMask, void *user_data);
+extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, uint32 interruptMask);
+
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
+extern void InitializeInterruptWaitSet(void);
 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/port/pgsleep.c b/src/port/pgsleep.c
index dfe824fe462..404d4086057 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 set 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 e01c0c9de93..03078078893 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 2a20ffb1273..1d019faa2e5 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -18,6 +18,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
+#include "postmaster/interrupt.h"
 #include "storage/shm_toc.h"
 #include "test_shm_mq.h"
 #include "utils/memutils.h"
@@ -285,11 +286,11 @@ 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);
+		(void) WaitInterrupt(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 we_bgworker_startup);
 
-		/* Reset the latch so we don't spin. */
-		ResetLatch(MyLatch);
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		/* An interrupt may have occurred while we were waiting. */
 		CHECK_FOR_INTERRUPTS();
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 443281addd0..240fe315d47 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -16,6 +16,7 @@
 #include "fmgr.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "postmaster/interrupt.h"
 #include "varatt.h"
 
 #include "test_shm_mq.h"
@@ -234,13 +235,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 an 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(INTERRUPT_GENERAL, WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
+			ClearInterrupt(INTERRUPT_GENERAL);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 96cd304dbbc..751cac60513 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -20,6 +20,7 @@
 #include "postgres.h"
 
 #include "miscadmin.h"
+#include "postmaster/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -128,7 +129,7 @@ test_shm_mq_main(Datum main_arg)
 		elog(DEBUG1, "registrant backend has exited prematurely");
 		proc_exit(1);
 	}
-	SetLatch(&registrant->procLatch);
+	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
 
 	/* 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 5b87d4f7038..1c23b4eadeb 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -5,7 +5,7 @@
  *		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.
+ *		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
@@ -26,7 +26,6 @@
 #include "miscadmin.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -213,15 +212,15 @@ 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(INTERRUPT_GENERAL,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 worker_spi_naptime * 1000L,
+							 worker_spi_wait_event_main);
+		ClearInterrupt(INTERRUPT_GENERAL);
 
 		CHECK_FOR_INTERRUPTS();
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9840060997f..93cf3ea31dd 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1271,6 +1271,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptType
 Interval
 IntervalAggState
 IntoClause
@@ -1509,7 +1510,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
-- 
2.39.5



  [text/x-patch] v7-0002-Fix-lost-wakeup-issue-in-logical-replication-laun.patch (4.1K, ../../[email protected]/3-v7-0002-Fix-lost-wakeup-issue-in-logical-replication-laun.patch)
  download | inline diff:
From 2425bf35af1e7bc690830fd0875fc7e08f274ae2 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:18 +0200
Subject: [PATCH v7 2/4] Fix lost wakeup issue in logical replication launcher

Fix it by using a separate interrupt bit for subscription changes.

Discussion: https://www.postgresql.org/message-id/[email protected]
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 src/backend/replication/logical/launcher.c | 22 ++++++++++++++--------
 src/include/postmaster/interrupt.h         |  5 ++++-
 2 files changed, 18 insertions(+), 9 deletions(-)

diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 071d93ce816..d915b9a9fce 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -56,7 +56,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;
@@ -804,7 +804,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;
 }
 
 /*
@@ -964,6 +964,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1109,8 +1110,12 @@ ApplyLauncherWakeupAtCommit(void)
 static 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);
 }
 
 /*
@@ -1124,8 +1129,8 @@ 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);
@@ -1157,6 +1162,7 @@ ApplyLauncherMain(Datum main_arg)
 		oldctx = MemoryContextSwitchTo(subctx);
 
 		/* Start any missing workers for enabled subscriptions. */
+		ClearInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE);
 		sublist = get_subscription_list();
 		foreach(lc, sublist)
 		{
@@ -1213,7 +1219,7 @@ ApplyLauncherMain(Datum main_arg)
 		MemoryContextDelete(subctx);
 
 		/* Wait for more work. */
-		rc = WaitInterrupt(INTERRUPT_GENERAL,
+		rc = WaitInterrupt(INTERRUPT_GENERAL | INTERRUPT_SUBSCRIPTION_CHANGE,
 						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
 						   wait_time,
 						   WAIT_EVENT_LOGICAL_LAUNCHER_MAIN);
@@ -1240,7 +1246,7 @@ ApplyLauncherMain(Datum main_arg)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/include/postmaster/interrupt.h b/src/include/postmaster/interrupt.h
index 346f9cae8e1..09dc6f0330d 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/postmaster/interrupt.h
@@ -113,11 +113,14 @@ typedef enum
 	INTERRUPT_GENERAL = 1 << 1,
 
 	/*
-	 * INTERRUPT_RECOVERY_WAKEUP is used to wake up startup process, to tell
+	 * INTERRUPT_RECOVERY_CONTINUE is used to wake up startup process, to tell
 	 * it that it should continue WAL replay. It's sent by WAL receiver when
 	 * more WAL arrives, or when promotion is requested.
 	 */
 	INTERRUPT_RECOVERY_CONTINUE = 1 << 2,
+
+	/* sent to logical replication launcher, when a subscription changes */
+	INTERRUPT_SUBSCRIPTION_CHANGE = 1 << 3,
 } InterruptType;
 
 /*
-- 
2.39.5



  [text/x-patch] v7-0003-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch (23.9K, ../../[email protected]/4-v7-0003-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch)
  download | inline diff:
From 0d2e581acb90d411a58994d3119963261cdf9b87 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Mon, 2 Dec 2024 15:55:21 +0200
Subject: [PATCH v7 3/4] Use INTERRUPT_GENERAL 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_GENERAL sent directly by the postmaster when the
worker state changes.  Previously, the postmaster would send a SIGUSR1
signal, which relied on the ProcSignal system's handler setting the
interrupt flag, with the same end effect.  Now that intermediate step
is skipped.

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

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

This represents the first use of SendInterrupt() in the postmaster.
It might seem more natural to use condition variables in the
wait-for-state-change functions, so that anyone with a handle can
wait, but condition variables as currently implemented would be a lot
less robust than simple interrupts.

Author: Thomas Munro <[email protected]>
Reviewed-by: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c            |   6 -
 doc/src/sgml/bgworker.sgml                  |  27 +++--
 src/backend/access/transam/parallel.c       |   1 -
 src/backend/postmaster/bgworker.c           | 128 +++++++++++---------
 src/backend/postmaster/interrupt.c          |   4 +-
 src/backend/postmaster/postmaster.c         |   8 +-
 src/backend/replication/logical/launcher.c  |   2 -
 src/backend/storage/ipc/waiteventset.c      |   5 +
 src/include/postmaster/bgworker.h           |  10 +-
 src/include/postmaster/bgworker_internals.h |   4 +-
 src/test/modules/test_shm_mq/setup.c        |   3 +-
 src/test/modules/test_shm_mq/test_shm_mq.h  |   2 +
 src/test/modules/test_shm_mq/worker.c       |   9 +-
 src/test/modules/worker_spi/worker_spi.c    |   3 -
 14 files changed, 112 insertions(+), 100 deletions(-)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 20dc7885d6a..a52058be9bb 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -816,9 +816,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),
@@ -852,9 +849,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/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 3171054e55d..871f6869d6a 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>
@@ -108,6 +108,18 @@ 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_GENERAL</literal> when the workers state changes, which allows the
+       caller to wait for the worker to start and shut down.  That can be
+       suppressed by setting this flag.
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -181,12 +193,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
@@ -258,10 +266,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/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 30cdce00b47..c4a1f69c619 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -604,7 +604,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.
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index c82971e9274..4425e404a35 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -78,6 +78,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;
 
 /*
@@ -192,8 +194,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -234,6 +237,14 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -315,20 +326,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;
+			int			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;
@@ -336,8 +347,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_GENERAL, notify_proc_number);
 
 			continue;
 		}
@@ -383,23 +394,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;
@@ -421,7 +415,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.
  */
@@ -466,8 +460,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_GENERAL, slot->notify_proc_number);
 }
 
 /*
@@ -483,12 +477,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
@@ -501,27 +495,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_GENERAL, notify_proc_number);
 }
 
 /*
- * Cancel SIGUSR1 notifications for a PID belonging to an exiting backend.
+ * Cancel notifications for a PID 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;
+		}
 	}
 }
 
@@ -553,14 +554,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;
+			int			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_GENERAL, notify_proc_number);
 		}
 	}
 }
@@ -613,11 +614,6 @@ ResetBackgroundWorkerCrashTimes(void)
 			 * resetting.
 			 */
 			rw->rw_crashed_at = 0;
-
-			/*
-			 * If there was anyone waiting for it, they're history.
-			 */
-			rw->rw_worker.bgw_notify_pid = 0;
 		}
 	}
 }
@@ -981,15 +977,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
@@ -1105,6 +1092,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.
@@ -1205,8 +1211,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)
@@ -1250,8 +1257,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)
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index 4cde7d52bd3..f325020ade2 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -112,6 +112,9 @@ RaiseInterrupt(uint32 interruptMask)
 
 /*
  * 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.
  */
 void
 SendInterrupt(uint32 interruptMask, ProcNumber pgprocno)
@@ -152,7 +155,6 @@ InitializeInterruptWaitSet(void)
 	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
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 82044abdb5d..f1332eb2195 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -4203,15 +4203,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_GENERAL, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index d915b9a9fce..168112a3220 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -495,7 +495,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))
@@ -938,7 +937,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);
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 1eba76a19c4..9380dd58d66 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -2059,6 +2059,11 @@ WakeupMyProc(void)
 void
 WakeupOtherProc(PGPROC *proc)
 {
+	/*
+	 * 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
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 058667a47a0..ea37157b2c8 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -59,6 +59,14 @@
  */
 #define BGWORKER_BACKEND_DATABASE_CONNECTION		0x0002
 
+/*
+ * 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							0x0004
+
 /*
  * 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
@@ -97,7 +105,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 29e6f44cf08..9b4d4d5166b 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len) pg_attribute_noreturn();
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 1d019faa2e5..02aa420360c 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -134,6 +134,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;
@@ -223,8 +224,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)
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 9ad9f63b44e..5b1b6cf8357 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 751cac60513..3d31c46269e 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -53,7 +53,6 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
 	 * Establish signal handlers.
@@ -123,13 +122,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);
-	}
-	SendInterrupt(INTERRUPT_GENERAL, GetNumberFromPGProc(registrant));
+	SendInterrupt(INTERRUPT_GENERAL, 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 1c23b4eadeb..28a2798037a 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -365,7 +365,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.
@@ -414,8 +413,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);
-- 
2.39.5



  [text/x-patch] v7-0004-Replace-ProcSignals-and-other-ad-hoc-signals-with.patch (240.3K, ../../[email protected]/5-v7-0004-Replace-ProcSignals-and-other-ad-hoc-signals-with.patch)
  download | inline diff:
From e623492590052f16841ffa171383766eb157074b Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 6 Mar 2025 18:16:16 +0200
Subject: [PATCH v7 4/4] Replace ProcSignals and other ad hoc signals with
 interrupts

This replaces the INTERRUPT_GENERAL, or setting the process's latch
before that, with more fine-grained interrupts. All the ProcSignals
are converted into interrupts, as well as things like timers and
config-reload requests.

Most callers of WaitInterrupt now use INTERRUPT_CFI_MASK(), which is
a bitmask that includes all the interrupts that are handled by
CHECK_FOR_INTERRUPTS() in the current state. CHECK_FOR_INTERRUPTS()
now clears the interrupts bits that it processes, the caller is no
longer required to do ClearInterrupt (or ResetLatch) for those
bits. If you wake up for other interrupts that are not handled by
CHECK_FOR_INTERRUPTS(), you still need to clear those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_GENERAL interrupt that is
multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

TODO:

 * ShutdownRequestPending became INTERRUPT_SHUTDOWN_AUX and
   ProcDiePending became INTERRUPT_DIE, but do we really need two
   different interrupts for requesting a process to terminate?

 * There are only a few interrupts bits left. Consolidate the existing
   ones, switch to a 64-bit atomic, or mulitplex some of them?

XXX: original text from Thomas's patch:

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

SIGUSR1 can now be set to SIG_IGN in most backends.  Some special cases
remain, that were not using the general sigusr1_handler (now removed).

Author: Thomas Munro <[email protected]>
Author: Heikki Linnakangas <[email protected]>
Discussion: https://postgr.es/m/CA%2BhUKG%2B3MkS21yK4jL4cgZywdnnGKiBg0jatoV6kzaniBmcqbQ%40mail.gmail.com
---
 contrib/pg_prewarm/autoprewarm.c              |  25 +-
 contrib/postgres_fdw/connection.c             |   3 +-
 doc/src/sgml/sources.sgml                     |   3 +-
 src/backend/access/heap/vacuumlazy.c          |   3 +-
 src/backend/access/spgist/spgdoinsert.c       |   9 +-
 src/backend/access/transam/README.parallel    |   2 +-
 src/backend/access/transam/parallel.c         |  50 +--
 src/backend/access/transam/xlog.c             |   3 +-
 src/backend/access/transam/xlogfuncs.c        |   6 +-
 src/backend/access/transam/xlogrecovery.c     |  31 +-
 src/backend/backup/basebackup_throttle.c      |  12 +-
 src/backend/commands/async.c                  | 103 +----
 src/backend/commands/dbcommands.c             |   1 +
 src/backend/commands/tablespace.c             |   1 +
 src/backend/commands/vacuum.c                 |   7 +-
 src/backend/executor/execAsync.c              |   2 +-
 src/backend/executor/nodeGather.c             |   2 +-
 src/backend/libpq/be-secure.c                 |  18 +-
 src/backend/libpq/pqcomm.c                    |  22 +-
 src/backend/libpq/pqmq.c                      |  22 +-
 src/backend/postmaster/autovacuum.c           |  43 +-
 src/backend/postmaster/bgworker.c             |   7 +-
 src/backend/postmaster/bgwriter.c             |  15 +-
 src/backend/postmaster/checkpointer.c         |  70 ++--
 src/backend/postmaster/interrupt.c            |  33 +-
 src/backend/postmaster/pgarch.c               |  20 +-
 src/backend/postmaster/postmaster.c           |   3 +-
 src/backend/postmaster/startup.c              |  16 +-
 src/backend/postmaster/syslogger.c            |   8 +-
 src/backend/postmaster/walsummarizer.c        |  26 +-
 src/backend/postmaster/walwriter.c            |   8 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |  12 +-
 .../replication/logical/applyparallelworker.c |  57 +--
 src/backend/replication/logical/launcher.c    |  42 +-
 src/backend/replication/logical/slotsync.c    |  57 +--
 src/backend/replication/logical/tablesync.c   |  12 +-
 src/backend/replication/logical/worker.c      |  19 +-
 src/backend/replication/slot.c                |  89 ++--
 src/backend/replication/slotfuncs.c           |  18 +-
 src/backend/replication/syncrep.c             |  13 +-
 src/backend/replication/walreceiver.c         |  19 +-
 src/backend/replication/walsender.c           |  78 ++--
 src/backend/storage/buffer/bufmgr.c           |  18 +-
 src/backend/storage/ipc/ipc.c                 |   6 +-
 src/backend/storage/ipc/procarray.c           |  20 +-
 src/backend/storage/ipc/procsignal.c          | 242 ++---------
 src/backend/storage/ipc/shm_mq.c              |  64 +--
 src/backend/storage/ipc/signalfuncs.c         |   2 +-
 src/backend/storage/ipc/sinval.c              |  52 +--
 src/backend/storage/ipc/sinvaladt.c           |  14 +-
 src/backend/storage/ipc/standby.c             |  81 ++--
 src/backend/storage/ipc/waiteventset.c        |   4 +-
 src/backend/storage/lmgr/condition_variable.c |   3 +-
 src/backend/storage/lmgr/predicate.c          |   5 +-
 src/backend/storage/lmgr/proc.c               |  15 +-
 src/backend/tcop/postgres.c                   | 349 +++++++---------
 src/backend/utils/activity/pgstat_database.c  |  20 +-
 src/backend/utils/adt/mcxtfuncs.c             |  16 +-
 src/backend/utils/adt/misc.c                  |   3 +-
 src/backend/utils/init/globals.c              |  15 -
 src/backend/utils/init/postinit.c             |  20 +-
 src/backend/utils/misc/timeout.c              |   6 -
 src/backend/utils/mmgr/mcxt.c                 |  20 +-
 src/include/access/parallel.h                 |   4 -
 src/include/commands/async.h                  |   6 -
 src/include/libpq/libpq-be-fe-helpers.h       |  14 +-
 src/include/libpq/pqmq.h                      |   2 +-
 src/include/miscadmin.h                       | 122 +-----
 src/include/pgstat.h                          |   2 +-
 src/include/postmaster/interrupt.h            | 386 +++++++++++++++---
 src/include/postmaster/startup.h              |   8 +
 src/include/replication/logicalworker.h       |   5 -
 src/include/replication/slot.h                |   6 +-
 src/include/replication/walsender.h           |   2 +-
 src/include/replication/walsender_private.h   |   1 +
 src/include/replication/worker_internal.h     |   5 +-
 src/include/storage/procarray.h               |   7 +-
 src/include/storage/procsignal.h              |  41 --
 src/include/storage/sinval.h                  |   5 -
 src/include/storage/standby.h                 |   4 +-
 src/include/storage/waiteventset.h            |   5 +-
 src/include/tcop/tcopprot.h                   |   6 +-
 src/include/utils/memutils.h                  |   1 -
 src/test/modules/test_shm_mq/setup.c          |   9 +-
 src/test/modules/test_shm_mq/test.c           |   6 +-
 src/test/modules/worker_spi/worker_spi.c      |   9 +-
 86 files changed, 1172 insertions(+), 1449 deletions(-)

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



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-03-06 16:43       ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-07-15 15:50         ` Andres Freund <[email protected]>
  1 sibling, 0 replies; 22+ messages in thread

From: Andres Freund @ 2025-07-15 15:50 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

A rebased version would be good, there are some not-entirely-trivial
conflicts.

> One notable change is that I merged storage/interrupt.[ch] with
> postmaster/interrupt.[ch], so the awkwardness of having two files with same
> name is gone.

I don't particularly like that. postmaster/interrupt.[ch] now is some very
general infrastructure, making postmaster/ inappropriate. Kinda wondering
about going the other way round.




On 2025-03-06 18:43:47 +0200, Heikki Linnakangas wrote:
> Subject: [PATCH v7 1/4] Replace Latches with Interrupts

> diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
> index 0ae9bf906ec..4cde7d52bd3 100644
> --- a/src/backend/postmaster/interrupt.c
> +++ b/src/backend/postmaster/interrupt.c
> @@ -1,13 +1,13 @@
>  /*-------------------------------------------------------------------------
>   *
>   * interrupt.c
> - *	  Interrupt handling routines.
> + *	  Inter-process interrupts
>   *
> - * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
> + * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
>   * Portions Copyright (c) 1994, Regents of the University of California
>   *
>   * IDENTIFICATION
> - *	  src/backend/postmaster/interrupt.c
> + *	  src/backend/storage/ipc/interrupt.c


I don't think this renaming was actually done :)



> +/*
> + * Switch to local interrupts.  Other backends can't send interrupts to this
> + * one.  Only RaiseInterrupt() can set them, from inside this process.
> + */
> +void
> +SwitchToLocalInterrupts(void)
> +{
> +	if (MyPendingInterrupts == &LocalPendingInterrupts)
> +		return;
> +
> +	MyPendingInterrupts = &LocalPendingInterrupts;
> +
> +	/*
> +	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
> +	 * seeing the new MyPendingInterrupts destination.
> +	 */
> +	pg_memory_barrier();
> +
> +	/*
> +	 * Mix in the interrupts that we have received already in our shared
> +	 * interrupt vector, while atomically clearing it.  Other backends may
> +	 * continue to set bits in it after this point, but we've atomically
> +	 * transferred the existing bits to our local vector so we won't get
> +	 * duplicated interrupts later if we switch backx.
> +	 */
> +	pg_atomic_fetch_or_u32(MyPendingInterrupts,
> +						   pg_atomic_exchange_u32(&MyProc->pendingInterrupts, 0));
> +}

pg_atomic_fetch_or_u32 is a barrier, so I'm not following why we need the
pg_memory_barrier().



> +/*
> + * Set an interrupt flag in this backend.
> + */
> +void
> +RaiseInterrupt(uint32 interruptMask)

I think it'd be good to mention that this needs to be async-signal-safe...


> +{
> +	uint32		old_pending;
> +
> +	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, interruptMask);

This makes interrupts a tad more expensive than before, previously we only had
a memory barrier (i.e. an atomic op on a backend-local var), now it's on
shared state.  Most likely fine.


> +	/*
> +	 * If the process is currently blocked waiting for an interrupt to arrive,
> +	 * and the interrupt wasn't already pending, wake it up.
> +	 */
> +	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
> +		WakeupMyProc();
> +}



> +/*
> + * Set an interrupt flag in another backend.
> + */
> +void
> +SendInterrupt(uint32 interruptMask, ProcNumber pgprocno)
> +{
> +	PGPROC	   *proc;
> +	uint32		old_pending;
> +
> +	Assert(pgprocno != INVALID_PROC_NUMBER);
> +	Assert(pgprocno >= 0);
> +	Assert(pgprocno < ProcGlobal->allProcCount);
> +
> +	proc = &ProcGlobal->allProcs[pgprocno];
> +	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, interruptMask);
> +
> +	/*
> +	 * If the process is currently blocked waiting for an interrupt to arrive,
> +	 * and the interrupt wasn't already pending, wake it up.
> +	 */
> +	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
> +		WakeupOtherProc(proc);
> +}

I wonder if we shouldn't share the implementation of these two functions...



> @@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
>  	/* Shutdown the recovery environment */
>  	if (standbyState != STANDBY_DISABLED)
>  		ShutdownRecoveryTransactionEnvironment();
> +
> +	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
>  }

What if we instead had a ProcGlobal->auxProc[auxproxtype]? We have different
versions of this for different types auf aux processes, which doesn't really
make sense.


> +		 * Perform a plain atomic read first as a fast path for the case that
> +		 * an interrupt is already pending.
>  		 */
> -		if (set->latch && !set->latch->is_set)
> +		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
> +		already_pending = ((old_mask & set->interrupt_mask) != 0);
> +
> +		if (!already_pending)
>  		{

Hm, I think this may be incorrect - what if there is *some* overlap between
MyPendingInterrupts and set->interrupt_mask, but they're not equal?


> +			/* 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;
> +			}

This is completely orthogonal, but: It bothers me that we use millisecons
rather than microseconds...



> diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
> index 749a79d48ef..4ebff9a4b37 100644
> --- a/src/backend/storage/lmgr/proc.c
> +++ b/src/backend/storage/lmgr/proc.c
> @@ -39,6 +39,7 @@
>  #include "miscadmin.h"
>  #include "pgstat.h"
>  #include "postmaster/autovacuum.h"
> +#include "postmaster/interrupt.h"
>  #include "replication/slotsync.h"
>  #include "replication/syncrep.h"
>  #include "storage/condition_variable.h"
> @@ -258,14 +259,28 @@ InitProcGlobal(void)
>  		Assert(fpPtr <= fpEndPtr);
>  
>  		/*
> -		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
> -		 * dummy PGPROCs don't need these though - they're never associated
> -		 * with a real process
> +		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
> +		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
> +		 * though - they're never associated with a real process
>  		 */
>  		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
>  		{
> +#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);
>  		}
>  

What's the story behind this aspect of the change? Does it have to be done as
part of a rather huge and largely mechanical commit?


> +/*
> + * Test an interrupt flag (of flags).
> + */
> +static inline bool
> +IsInterruptPending(uint32 interruptMask)
> +{
> +	return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0;
> +}

Do we need to care about memory ordering?

>
> +extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
> +extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;

I'm not a huge fan of these being declared in the same file as the low-level
implementation of interrupts...



> From e623492590052f16841ffa171383766eb157074b Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Thu, 6 Mar 2025 18:16:16 +0200
> Subject: [PATCH v7 4/4] Replace ProcSignals and other ad hoc signals with
>  interrupts
> 
> This replaces the INTERRUPT_GENERAL, or setting the process's latch
> before that, with more fine-grained interrupts. All the ProcSignals
> are converted into interrupts, as well as things like timers and
> config-reload requests.
> 
> Most callers of WaitInterrupt now use INTERRUPT_CFI_MASK(), which is
> a bitmask that includes all the interrupts that are handled by
> CHECK_FOR_INTERRUPTS() in the current state. CHECK_FOR_INTERRUPTS()
> now clears the interrupts bits that it processes, the caller is no
> longer required to do ClearInterrupt (or ResetLatch) for those
> bits. If you wake up for other interrupts that are not handled by
> CHECK_FOR_INTERRUPTS(), you still need to clear those.
> 
> Now that there are separate bits for different things, it's possible
> to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
> INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
> query cancellation, and not wake up on other events like a shutdown
> request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
> still processes those interrupts if you call it, and you would want to
> process all the standard interrupts promptly anyway. It might be
> useful to check for specific interrupts with InterruptPending(),
> without immediately processing them, however. spgdoinsert() does
> something like that. In this commit I didn't change its overall logic,
> but it probably could be made more fine-grained now.
> 
> More importantly, you can now have additional interrupts that are
> processed less frequently and do not wake up the backend when it's not
> prepared to process them. For example, the SINVAL_CATCHUP interrupt is
> only processed when the backend is idle. Previously, a catchup request
> would wake up the process whether it was ready to handle it or not;
> now it only wakes up when the backend is idle. That's not too
> significant for catchup interrupts because they don't occur that often
> anyway, but it's still nice and could be more significant with other
> interrupts.
> 
> There's still a general-purpose INTERRUPT_GENERAL interrupt that is
> multiplexed for many different purposes in different processes, for
> example to wake up the walwriter when it has work to do, and to wake
> up processes waiting on a condition variable. The common property of
> those uses is that there's some other flag or condition that is
> checked on each wakeup, the wakeup interrupt itself means merely that
> something interesting might've happened.

I guess extensions that just need to use INTERRUPT_GENERAL, given that new
interrupt reasons can't be dynamically registered?


> diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
> index c244f6c5ec0..4837739a868 100644
> --- a/src/backend/access/transam/xlogrecovery.c
> +++ b/src/backend/access/transam/xlogrecovery.c
> @@ -3004,18 +3004,12 @@ recoveryApplyDelay(XLogReaderState *record)
>  
>  	while (true)
>  	{
> -		/*
> -		 * INTERRUPT_GENERAL is used for all the usual interrupts, like config
> -		 * reloads.  The wakeups when more WAL arrive use a different
> -		 * interrupt number (INTERRUPT_RECOVERY_CONTINUE) so that more WAL
> -		 * arriving don't wake up the startup process excessively when we're
> -		 * waiting in other places, like for recovery conflicts.
> -		 */
> -		ClearInterrupt(INTERRUPT_GENERAL);
> -
>  		/* This might change recovery_min_apply_delay. */
>  		ProcessStartupProcInterrupts();
>  
> +		/*
> +		 * INTERRUPT_RECOVERY_CONTINUE indicates that more WAL has arrived.
> +		 */
>  		ClearInterrupt(INTERRUPT_RECOVERY_CONTINUE);
>  		if (CheckForStandbyTrigger())
>  			break;

Why is it named RECOVERY_CONTINUE rather than WAL_ARRIVED or such?



> @@ -163,23 +165,15 @@ throttle(bbsink_throttle *sink, size_t increment)
>  		if (sleep <= 0)
>  			break;
>  
> -		ClearInterrupt(INTERRUPT_GENERAL);
> -
> -		/* We're eating a wakeup, so check for interrupts */
> -		CHECK_FOR_INTERRUPTS();
> -
>  		/*
>  		 * (TAR_SEND_SIZE / throttling_sample * elapsed_min_unit) should be
>  		 * the maximum time to sleep. Thus the cast to long is safe.
>  		 */
> -		wait_result = WaitInterrupt(INTERRUPT_GENERAL,
> +		wait_result = WaitInterrupt(INTERRUPT_CFI_MASK(),
>  									WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
>  									(long) (sleep / 1000),
>  									WAIT_EVENT_BASE_BACKUP_THROTTLE);
>  
> -		if (wait_result & WL_INTERRUPT)
> -			CHECK_FOR_INTERRUPTS();
> -

Not specific to this code: Why is INTERRUPT_CFI_MASK() a function like macro?



> @@ -1834,11 +1779,10 @@ HandleNotifyInterrupt(void)
>  void
>  ProcessNotifyInterrupt(bool flush)
>  {
> -	if (IsTransactionOrTransactionBlock())
> -		return;					/* not really idle */
> +	Assert(!IsTransactionOrTransactionBlock());
>  
>  	/* Loop in case another signal arrives while sending messages */
> -	while (notifyInterruptPending)
> +	while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
>  		ProcessIncomingNotify(flush);
>  }

It's not new as of this change, but doesn't this have the danger that we
process notifies endlessly while not accepting e.g. a termination interrupt?


> +	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
>  	{
>  		int			autovacuum_max_workers_prev = autovacuum_max_workers;
>  
> -		ConfigReloadPending = false;
>  		ProcessConfigFile(PGC_SIGHUP);
>  
>  		/* shutdown requested in config file? */
> @@ -771,11 +774,11 @@ ProcessAutoVacLauncherInterrupts(void)
>  	}
>  
>  	/* Process barrier events */
> -	if (ProcSignalBarrierPending)
> +	if (IsInterruptPending(INTERRUPT_BARRIER))
>  		ProcessProcSignalBarrier();

Why do we have some code immediately consuming and others just checking if an
interrupt is pending?


> @@ -572,10 +574,18 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
>  			cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
>  		}
>  
> -		(void) WaitInterrupt(INTERRUPT_GENERAL,
> -							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
> -							 cur_timeout * 1000L /* convert to ms */ ,
> -							 WAIT_EVENT_CHECKPOINTER_MAIN);
> +		(void) WaitInterrupt(
> +			/* these are handled in the main loop */
> +			INTERRUPT_GENERAL | 	/* checkpoint requested */

Why is a checkpoint request done via GENERAL? Seems that's specifically one
that we do *not* want to absorb via CFI?


> +			INTERRUPT_SHUTDOWN_XLOG |
> +			INTERRUPT_SHUTDOWN_AUX |
> +			/* ProcessCheckpointerInterrupts() */
> +			INTERRUPT_BARRIER |
> +			INTERRUPT_LOG_MEMORY_CONTEXT |
> +			INTERRUPT_CONFIG_RELOAD,

It seems like this will somewhat painful to maintain over time. Whenever some
relatively generic type of interrupt is added, all the aux processes need to
be updated...


> @@ -584,7 +594,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
>  	 */
>  	ExitOnAnyError = true;
>  
> -	if (ShutdownXLOGPending)
> +	if (IsInterruptPending(INTERRUPT_SHUTDOWN_XLOG))
>  	{
>  		/*
>  		 * Close down the database.
> @@ -602,7 +612,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
>  		 * Tell postmaster that we're done.
>  		 */
>  		SendPostmasterSignal(PMSIGNAL_XLOG_IS_SHUTDOWN);
> -		ShutdownXLOGPending = false;
> +		ClearInterrupt(INTERRUPT_SHUTDOWN_XLOG);
>  	}

Why not use ConsumeInterrupt()?


> @@ -283,20 +287,17 @@ WaitInterruptOrSocket(uint32 interruptMask, int wakeEvents, pgsocket sock,
>  void
>  ProcessMainLoopInterrupts(void)
>  {
> -	if (ProcSignalBarrierPending)
> +	if (IsInterruptPending(INTERRUPT_BARRIER))
>  		ProcessProcSignalBarrier();
>  
> -	if (ConfigReloadPending)
> -	{
> -		ConfigReloadPending = false;
> +	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
>  		ProcessConfigFile(PGC_SIGHUP);
> -	}
>  
> -	if (ShutdownRequestPending)
> +	if (IsInterruptPending(INTERRUPT_SHUTDOWN_AUX))
>  		proc_exit(0);
>  
>  	/* Perform logging of memory contexts of this process */
> -	if (LogMemoryContextPending)
> +	if (IsInterruptPending(INTERRUPT_LOG_MEMORY_CONTEXT))
>  		ProcessLogMemoryContextInterrupt();
>  }

This got more expensive with this change. Presumably matters more for
ProcessInterrupts, but I haven't gotten there yet :)

The problem is that each of the IsInterruptPending/ConsumeInterrupt calls will
do its own read of the interrupt mask, which now includes a read memory
barrier. On Arm or such that means we'll inject multiple subsequent memory
barriers into code that was just checking a few local vars beforehand.



> @@ -304,14 +305,13 @@ ProcessMainLoopInterrupts(void)
>   * Simple signal handler for triggering a configuration reload.
>   *
>   * Normally, this handler would be used for SIGHUP. The idea is that code
> - * which uses it would arrange to check the ConfigReloadPending flag at
> - * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
> + * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt at
> + * convenient places inside main loops, or else call HandleMainLoopInterrupts.
>   */
>  void
>  SignalHandlerForConfigReload(SIGNAL_ARGS)
>  {
> -	ConfigReloadPending = true;
> -	RaiseInterrupt(INTERRUPT_GENERAL);
> +	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
>  }

Somewhat funny that we continue to use SIGHUP from another process, just to
then raise an interrupt...


>  		if (rc & WL_INTERRUPT)
>  		{
> -			ClearInterrupt(INTERRUPT_GENERAL);
>  			CHECK_FOR_INTERRUPTS();
> +			ClearInterrupt(INTERRUPT_GENERAL);
>  		}

Huh, what is this type of change about?


> @@ -3488,13 +3489,13 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
>   * Returns pid of the process signaled, or 0 if not found.
>   */
>  pid_t
> -CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
> +CancelVirtualTransaction(VirtualTransactionId vxid, InterruptType reason)
>  {
> -	return SignalVirtualTransaction(vxid, sigmode, true);
> +	return SignalVirtualTransaction(vxid, reason, true);
>  }
>  
>  pid_t
> -SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
> +SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason,
>  						 bool conflictPending)
>  {
>  	ProcArrayStruct *arrayP = procArray;
> @@ -3522,7 +3523,7 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
>  				 * Kill the pid if it's still here. If not, that's what we
>  				 * wanted so ignore any errors.
>  				 */
> -				(void) SendProcSignal(pid, sigmode, vxid.procNumber);
> +				SendInterrupt(reason, vxid.procNumber);
>  			}
>  			break;
>  		}

This made me look at SignalVirtualTransaction() and I'm somewhat shocked. What
on earth are we doing?

pid_t
SignalVirtualTransaction(VirtualTransactionId vxid, InterruptType reason,
						 bool conflictPending)
{
	ProcArrayStruct *arrayP = procArray;
	int			index;
	pid_t		pid = 0;

	LWLockAcquire(ProcArrayLock, LW_SHARED);

	for (index = 0; index < arrayP->numProcs; index++)
	{
		int			pgprocno = arrayP->pgprocnos[index];
		PGPROC	   *proc = &allProcs[pgprocno];
		VirtualTransactionId procvxid;

		GET_VXID_FROM_PGPROC(procvxid, *proc);

		if (procvxid.procNumber == vxid.procNumber &&
			procvxid.localTransactionId == vxid.localTransactionId)
		{
			proc->recoveryConflictPending = conflictPending;
			pid = proc->pid;
			if (pid != 0)
			{
				/*
				 * Kill the pid if it's still here. If not, that's what we
				 * wanted so ignore any errors.
				 */
				SendInterrupt(reason, vxid.procNumber);
			}
			break;
		}
	}

	LWLockRelease(ProcArrayLock);

	return pid;
}

We're iterating over the whole procarray, for each proc entry, we extract the
procNumber from the vxid and the backend and check if they're the same.  Even
though the procNumber is the friggin index into the procarray!  What the hell?



> @@ -947,6 +953,7 @@ void
>  StandbyDeadLockHandler(void)
>  {
>  	got_standby_deadlock_timeout = true;
> +	RaiseInterrupt(INTERRUPT_GENERAL);
>  }
>  
>  /*
> @@ -956,6 +963,7 @@ void
>  StandbyTimeoutHandler(void)
>  {
>  	got_standby_delay_timeout = true;
> +	RaiseInterrupt(INTERRUPT_GENERAL);
>  }
>  
>  /*
> @@ -965,6 +973,7 @@ void
>  StandbyLockTimeoutHandler(void)
>  {
>  	got_standby_lock_timeout = true;
> +	RaiseInterrupt(INTERRUPT_GENERAL);
>  }

Why are these using INTERRUPT_GENERAL?


> +
> +	if (ConsumeInterrupt(INTERRUPT_WALSND_INIT_STOPPING))
> +		ProcessWalSndInitStopping();

Huh. So ProcessWalSndInitStopping() raises different inerrupts to actually
exit and thus relies on being called first? That seems rather clunky.


> diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
> index f5b773b79e5..08de6146800 100644
> --- a/src/include/miscadmin.h
> +++ b/src/include/miscadmin.h
> @@ -28,124 +28,28 @@
>  #include "datatype/timestamp.h" /* for TimestampTz */
>  #include "pgtime.h"				/* for pg_time_t */
>  
> +/*
> + * for backwards-compatibility: CHECK_FOR_INTERRUPTS() used to be here, and it's
> + * used all over the place
> + */
> +#ifndef FRONTEND
> +#include "postmaster/interrupt.h"
> +#endif

Seems rather bonkers for frontend code to include this. I see that it's
required right now, but man, that feels wrong.

I don't think it's great to include atomics.h, waiteventset.h in quite that
many backend files either.


> + * Waiting on an interrupt
> + * -----------------------
> + *
> + * The correct pattern to wait for event(s) using INTERRUPT_GENERAL (or any
> + * bespoken interrupt flag) is:
>   *
>   * for (;;)
>   * {
> + *	   CHECK_FOR_INTERRUPTS();
> + *
>   *	   ClearInterrupt(INTERRUPT_GENERAL);
>   *	   if (work to do)
>   *		   Do Stuff();
> - *	   WaitInterrupt(INTERRUPT_GENERAL, ...);
> + *	   WaitInterrupt(INTERRUPT_CFI_MASAK() | INTERRUPT_GENERAL, ...);
>   * }
>   *
>   * It's important to clear the interrupt *before* checking if there's work to
> - * do. Otherwise, if someone sets the interrupt between the check and the
> + * do.  Otherwise, if someone sets the interrupt between the check and the
>   * ClearInterrupt() call, you will miss it and Wait will incorrectly block.

Isn't the change to move CHECK_FOR_INTERRUPTS() before ClearInterrupt()
violating what the paragraph explains?


>  /*
>   * Flags in the pending interrupts bitmask. Each value represents one bit in
>   * the bitmask.
>   */
> -typedef enum
> +typedef enum InterruptType
>  {

I'm rather concerned about the number of interrupt bits we've already
consume. I'll respond about that in a separate, higher-level, email.


>  /*
> - * Clear an interrupt flag.
> + * Clear an interrupt flag (or flags).
>   */
>  static inline void
>  ClearInterrupt(uint32 interruptMask)
>  {
>  	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~interruptMask);
> +	pg_write_barrier();
>  }

pg_atomic_fetch_and_u32 is a full barrier, no separate barrier needed.



>  #endif
> diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
> index 158f52255a6..a0316202b95 100644
> --- a/src/include/postmaster/startup.h
> +++ b/src/include/postmaster/startup.h
> @@ -25,6 +25,14 @@
>  
>  extern PGDLLIMPORT int log_startup_progress_interval;
>  
> +/* The set of interrupts that are processed by ProcessStartupProcInterrupts */
> +#define INTERRUPT_STARTUP_PROC_MASK	(			\
> +		INTERRUPT_BARRIER |						\
> +		INTERRUPT_DIE |							\
> +		INTERRUPT_LOG_MEMORY_CONTEXT |			\
> +		INTERRUPT_CONFIG_RELOAD					\
> +		)

Somehow I find this name a bit confusing, the first parse attempt ends up with
PROC_MASK as one of the components of the name.  How about
INTERRUPT_MASK_STARTUP[_PROC]?



Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-03-06 16:43       ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-07-23 07:42         ` Joel Jacobson <[email protected]>
  2025-07-24 22:04           ` Re: Interrupts vs signals Joel Jacobson <[email protected]>
  1 sibling, 1 reply; 22+ messages in thread

From: Joel Jacobson @ 2025-07-23 07:42 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Thu, Mar 6, 2025, at 17:43, Heikki Linnakangas wrote:
> On 06/03/2025 02:47, Heikki Linnakangas wrote:
>> Here's a new patch set. It includes the previous work, and also goes the 
>> whole hog and replaces procsignals and many other signalling with 
>> interrupts. It's based on Thomas's v3-0002-Redesign-interrupts-remove- 
>> ProcSignals.patch much earlier in this thread.
>
> And here's yet another version. It's the same at high level, but with a 
> ton of little fixes.
>
> One notable change is that I merged storage/interrupt.[ch] with 
> postmaster/interrupt.[ch], so the awkwardness of having two files with 
> same name is gone.
>
> -- 
> Heikki Linnakangas
> Neon (https://neon.tech)
>
> Attachments:
> * v7-0001-Replace-Latches-with-Interrupts.patch
> * v7-0002-Fix-lost-wakeup-issue-in-logical-replication-laun.patch
> * v7-0003-Use-INTERRUPT_GENERAL-for-bgworker-state-change-n.patch
> * v7-0004-Replace-ProcSignals-and-other-ad-hoc-signals-with.patch

Great work in this thread. I'll try to help, definitively benchmarking,
but will also try to load the new design into my brain, to get the
correct mental model of it, so I can hopefully help with code review as
well.

I'm trying to figure out what all possible states a backend can be in.
With the new design, it basically seems like there are two different
types of bits? An Interrupt bit type, and a Sleeping bit. This gives
four possible states, if I'm not mistaken.

I say "bit type" since there are of course many different bits, for all
the existing interrupts, previously called "reasons" in the old design.
But all those type of interrupt bits, are just one type of bit. The
other type of bit seems to be the "sleeping bit".

I note how the InterruptType is overloaded to also be used for the
sleeping bit (SLEEPING_ON_INTERRUPTS).

Would it be correct to say that a backend can be in any of these four
states?

1. Interrupt clear, Sleeping clear: The backend is running its code and
is not waiting for anything.

2. Interrupt set, Sleeping clear: Interrupt pending. A sender has set
our interrupt bit, but we were still executing code. Crucially, the
sender saw the Sleeping bit was clear and thus did not send a physical
wakeup. The pending interrupt will be serviced at the next
CHECK_FOR_INTERRUPTS() call.

3. Interrupt clear, Sleeping set: Preparing to block. This is the
critical, short-lived state that solves the lost-wakeup problem. We have
just atomically set the Sleeping bit, advertising our intent to enter a
kernel wait. Any sender that sees this state knows it is now responsible
for sending a physical wakeup signal.

4. Interrupt set, Sleeping set: Wakeup in progress. This state confirms
the race was handled. A sender caught us in state 3, flipped our
Interrupt bit, saw our Sleeping bit was set, and sent the required
physical wakeup. We will enter the kernel wait but return immediately,
service the interrupt, and reset both bits.

If my mental model of your new design is correct, I was expecting to see similar
performance gains for LISTEN/NOTIFY, thanks to not unnecessarily interrupting
listening backends.

To test, I checked out an old git hash (d611f8b) from 2025-03-06, so I
could apply the v7 patch set.

I then ran one of the benchmark scripts that I've created for my work on
async.c.

It's a great win compared to master, but for some reason, your v7 patch
set is a bit slower on all benchmark configs I tested, except -j 1 -c 1,
where it's the same.

 Connections=Jobs | TPS (patch-joel) | TPS (patch-v7) | Relative Diff (%) | StdDev (master) | StdDev (patch)
------------------+------------------+----------------+-------------------+-----------------+----------------
                1 |           151510 |         151119 | -0.26%            |             923 |            577
                2 |           239051 |         227925 | -4.65%            |            1596 |            638
                4 |           250910 |         235238 | -6.25%            |            4891 |           5628
                8 |           171944 |         167271 | -2.72%            |            2752 |           5182
               16 |           165482 |         148305 | -10.38%           |            2825 |           6447
               32 |           145150 |         140216 | -3.40%            |            1566 |           1867
               64 |           131836 |         122617 | -6.99%            |             573 |            253
              128 |           121333 |         113668 | -6.32%            |             874 |            981
(8 rows)

This might be due to other changes since Mars, since my patch is against
master.

When you've rebased, I can run the benchmarks again, and try to find the
culprit.

Btw, I really like the idea of a "stepping stone" approach, mentioned by
Thomas Munro in the other thread [1]. That is, looking at if we can find
ways to do smaller incremental changes, with a great ratio between
performance gains and additional complexity cost, for every committable
step.

/Joel

[1] https://www.postgresql.org/message-id/CA%2BhUKGKVDSqwzwiptWsRbYLkTMQKexVoQ4V2z%3DQkMVvzfpdA%2BQ%40ma...





^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-03-06 16:43       ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-23 07:42         ` Re: Interrupts vs signals Joel Jacobson <[email protected]>
@ 2025-07-24 22:04           ` Joel Jacobson <[email protected]>
  0 siblings, 0 replies; 22+ messages in thread

From: Joel Jacobson @ 2025-07-24 22:04 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; Andres Freund <[email protected]>; Thomas Munro <[email protected]>; +Cc: Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On Wed, Jul 23, 2025, at 09:42, Joel Jacobson wrote:
> Great work in this thread. I'll try to help, definitively benchmarking,
> but will also try to load the new design into my brain, to get the
> correct mental model of it, so I can hopefully help with code review as
> well.

First, my apologies for joining this long-running discussion late.

Following up on my email in the "Optimize LISTEN/NOTIFY" thread [1], I
wanted to elaborate on the architectural pattern my patches demonstrate,
as I believe it's highly complementary to the work being done here.

The fundamental distinction is between decoupled vs. combined state
management. The v7 patch set creates a new Interrupt system where the
wakeup mechanism and the 'pending' state for all subsystems are combined
into a single, centralized atomic bitmask.

My work explores an alternative: decoupling the two. The insight is that
many of our legacy subsystems were designed to be stateless because they
lacked the atomics and WaitEventSet abstractions we have today. The
signal was a workaround for this state management limitation.

This suggests a powerful, three-step migration pattern:

Step 1: Decouple state management with a lock-free atomic FSM.

Each subsystem should be empowered to manage its own state. My first
patch for async.c [1] demonstrates this by introducing a lock-free,
atomic finite state machine (FSM). Using a subsystem-specific atomic
integer and CAS operations, async.c now robustly manages its own IDLE,
SIGNALLED, PROCESSING states without any locks. This solves the state
synchronization problem directly and eliminates the vast majority of
redundant wakeups.

Step 2: Trivialize the wake-up with a generic poke.

Once state is managed reliably, the expensive kill(pid, SIGUSR1) syscall
can be trivially replaced with a direct SetLatch, as shown in my second
patch [1]. This is a simple and effective intermediate step that leverages
the existing WaitEventSet infrastructure to make the wakeup much
cheaper.

Step 3: Decouple event dispatch with specific interrupts.

The final goal is to replace the generic poke with SendInterrupt using a
specific reason (e.g., INTERRUPT_ASYNC_NOTIFY). This lets the event loop
wait on a bitmask of interrupt reasons and, on wakeup, dispatch only to
the relevant subsystem handler—fully decoupling event handling from
subsystem details.

This three-step, vertical migration strategy (subsystem by subsystem)
seems to offer a powerful alternative to a horizontal, layer-by-layer
replacement. It allows each subsystem's logic to be self-contained and
avoids constraints on a single global interrupt mask. The core
WaitEventSet is kept simple: its only job is to provide the efficient,
multiplexed wait.

I believe the ideal architecture uses your unified Interrupt system as
the low-level primitive, while encouraging key subsystems to adopt these
decoupled FSMs to determine *when* to call SendInterrupt.

I'm happy to help with this effort in any way I can.

/Joel

[1] https://www.postgresql.org/message-id/0b4d402a-9ac2-4aa8-acf8-8231dbe579ea%40app.fastmail.com





^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2025-07-15 16:50       ` Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  1 sibling, 1 reply; 22+ messages in thread

From: Andres Freund @ 2025-07-15 16:50 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

On 2025-03-06 02:47:38 +0200, Heikki Linnakangas wrote:
> > I was chatting with Heikki about this patch and he mentioned that he recalls a
> > patch that did some work to unify the signal replacement, procsignal.h and
> > CHECK_FOR_INTERRUPTS(). Thomas, that was probably from you? Do you have a
> > pointer, if so?
> > 
> > It does seem like we're going to have to do some unification here. We have too
> > many different partially overlapping, partially collaborating systems here.
> > 
> > 
> > - procsignals - kinda like the interrupts here, but not quite. Probably can't
> >   just merge them 1:1 into the proposed mechanism, or we'll run out of bits
> >   rather soon.  I don't know if we want the relevant multiplexing to be built
> >   into interrupt.h or not.
> > 
> >   Or we ought to redesign this mechanism to deal with more than 32 types of
> >   interrupt.
> 
> In this patch, 29 out of 32 bits are used. Yeah, that doesn't leave much
> headroom.

That does seem way too tight, particularly because the patch has several uses
of GENERAL that shouldn't really be using that.


> Some ideas for addressing that:
> 
> 1. Use 64-bit atomics. Are there any architectures left where that would not
> be acceptable?

I had thought that armv7 would be a problem, but it isn't - it just doesn't
have 8 byte atomicity for reads/writes that aren't explicitly atomic.  Afaict
that leaves us with <= i486 and <= armv6 as not supporting 64bit atomics -
which I think we could desupport.  I guess I'll start a a thread about that.


> 2. Use 64-bit integers, but for the actual signaling part, split it into two
> 32-bit atomic words. Waiting on the interrupts would need to check both, but
> that seems fine from a performance point of view.
> 
> 3. If we need > 64 bits, things get a bit awkward as you can no longer have
> a single integer to represent a bitmask that includes all the bits. That
> makes the function signatures more ugly, you need to have two arguments like
> interruptMask1 and interruptMask2 or somehting. But should basically work
> otherwise.
> 
> 4. Multiplex the interrupts so that we need fewer of them. I think all the
> recovery conflict interrupts could be merged into one, for example, if we
> had a separate bitmask somewhere else in PGPROC to indicate which ones are
> set. The limitation would be that if interrupts are multiplexed together
> into one bit, you could only wait for all or none of them.

If we go that way, I suspect we should bake this into the interrupt.[ch]
system itself, rather than open-code it in a bunch of places.  We could
e.g. split the current InterruptType into a class and type portion - that
actually also might help with writing more efficient interrupt processing
functions.  I'm imagining that the main atomic would just contain bits for
each interrupt class, with an array of atomics for the sub-portions of a
class. That would mean you couldn't wait on sub-parts of a class, but that
seems fine.

Another advantage of having something like interrupt classes could be that it
would make it a bit easier for code to specify what interrupts can be
processed. Instead of having to know about all the different
INTERRUPT_*_TIMEOUT or INTERRUPT_*{MESSAGE,LOG}* interrupt IDs code code just
specify whether it can deal with e.g. messages being processed.


> I'd like to not have to treat these bits as a scarce resource. It'd be nice
> to use even more distinct interrupts than what's in the patch now for
> different things, just for clarity. And I'd like to make it possible for
> extensions to allocate interrupt bits too. (Not included in these patches
> yet)

> I'm leaning towards 1. or 2. at the moment. 64 bits should be enough for a
> long time.

I'm not so sure about that, particularly not if interrupts can be allocated in
extensions.


I'm not really in love how this looks like yet, tbh. Aspects of it seem to buy
further into some of our already existing design mistakes. I have two main
concerns:

1) It seems rather bonkers that we have to go to every process type and add
code for INTERRUPT_LOG_MEMORY_CONTEXT or INTERRUPT_CONFIG_RELOAD.

2) The fact that a lot of code specifies explicit interrupt masks makes that
code harder to compose, because whether we can process some interrupt or not
depends on higher level state.  That's e.g. why INTERRUPT_CFI_MASK() has to be
a bit magic and decid ewhat interrupt mask to use and why we need a separate
interrupt handling functions for interrupts happening while waiting for
network IO than normally.


For 1), I suspect that we should have a central mapping array between
InterruptTypes and the code to handle each of the interrupt types.  That
mapping table also could serve as the registry for extensions to register
their own interrupt ids.

For 2), I wonder if we ought to have a global mask of interrupt kinds that can
be processed in some context. Instead of having INTERRUPT_CFI_MASK() compute
what mask to use, we could have things like HOLD_CANCEL_INTERRUPTS be defined
as something like

if (InterruptHoldCount[CANCEL]++ == 0)
   InterruptMask &= ~CANCEL;

which would allow CHECK_FOR_INTERRUPTS to just use InterruptMask to check for
to-be-processed interrupts.


Greetings,

Andres Freund





^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2026-01-29 01:05         ` Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2026-01-29 01:05 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Here's a new rebased and massively re-worked patch.

The patches are split differently than in the previous version:
- Patches 0001-0005 are just refactoring around recovery conflicts which 
I posted on a separate thread [1]. Please review and comment there.
- Patches 0006 and 0007 are small cleanups that could be applied early 
(after updating the docs, as noted in TODO comment there).
- All the interesting bits for this thread are in the last, massive patch.

To review this, I suggest starting from the new 
src/backend/ipc/README.md file. It gives a good overview of the 
mechanism (or if it doesn't, that's valuable feedback :-) ). It also 
contains a bunch of Open Questions at the bottom; I'd love to hear 
opinions and ideas on those.

Wrt. the issue of running out of bits: My current plan is to just switch 
to 64-bit interrupt masks. That gives a fair amount of headroom. Would 
be nice to have even more headroom, but I think 64 bits will be enough 
for a long time.

The big high-level design change in this version is to address these issues:

On 15/07/2025 19:50, Andres Freund wrote:
> I'm not really in love how this looks like yet, tbh. Aspects of it seem to buy
> further into some of our already existing design mistakes. I have two main
> concerns:
> 
> 1) It seems rather bonkers that we have to go to every process type and add
> code for INTERRUPT_LOG_MEMORY_CONTEXT or INTERRUPT_CONFIG_RELOAD.
> 
> 2) The fact that a lot of code specifies explicit interrupt masks makes that
> code harder to compose, because whether we can process some interrupt or not
> depends on higher level state.  That's e.g. why INTERRUPT_CFI_MASK() has to be
> a bit magic and decid ewhat interrupt mask to use and why we need a separate
> interrupt handling functions for interrupts happening while waiting for
> network IO than normally.
> 
> 
> For 1), I suspect that we should have a central mapping array between
> InterruptTypes and the code to handle each of the interrupt types.  That
> mapping table also could serve as the registry for extensions to register
> their own interrupt ids.

That's a great idea, and that's what this patch now does. You can now 
register handler functions for interrupts which will then be called by 
CHECK_FOR_INTERRUPTS() whenever the corresponding interrupt is pending. 
ProcessInterrupts has no knowledge about what each interrupt means, it 
just calls the handler functions in an array.

At backend startup, a "default" set of interrupt handlers are installed, 
like this: (a shortened excerpt from SetStandardInterruptHandlers())

	/* 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);

	...

Many processes have additional interrupts, or want different handlers 
for the standard interrupts. They make additional SetInterruptHandler() 
calls to set up their process-specific handlers.

This largely solves problems 1) and 2). I replaced the 
INTERRUPT_CFI_MASK() macro with a global variable 
CheckForInterruptsMask. At all times, it is a bitmask of all the 
interrupts that CHECK_FOR_INTERRUPTS() would process and clear. Even 
HOLD/RESUME_INTERRUPTS() now updates CheckForInterruptsMask (setting it 
to zero in HOLD_INTERRUPTS(), and restoring it on RESUME_INTERRUPTS())

With that facility, a lot of things got nicer. For example, I got rid of 
ProcessClientReadInterrupt() altogether. secure_read() now calls plain 
old CHECK_FOR_INTERRUPTS(), and it is the caller's responsibility to 
enable/disable the interrupt handlers according to what should or should 
not be processed. (ProcessClientWriteInterrupt() is gone too, although 
now that I look at it, I wonder if that went too far and we should still 
refrain from processing interrupts other than INTERRUPT_TERMINATE while 
we're sending to the client...)

I'd love to get feedback on this mechanism. And on the patch in general, 
of course, but this is the big new part.

> For 2), I wonder if we ought to have a global mask of interrupt kinds that can
> be processed in some context. Instead of having INTERRUPT_CFI_MASK() compute
> what mask to use, we could have things like HOLD_CANCEL_INTERRUPTS be defined
> as something like
> 
> if (InterruptHoldCount[CANCEL]++ == 0)
>     InterruptMask &= ~CANCEL;
> 
> which would allow CHECK_FOR_INTERRUPTS to just use InterruptMask to check for
> to-be-processed interrupts.

I played with that, and it's quite sensible, but then I realized that we 
only use HOLD_CANCEL_INTERRUPTS() in a few places, namely when we are 
reading a message from the client. We don't really need the nesting 
capability, i.e. we don't need a counter, a boolean is enough.


On 15/07/2025 18:50, Andres Freund wrote:
>> @@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
>>  	/* Shutdown the recovery environment */
>>  	if (standbyState != STANDBY_DISABLED)
>>  		ShutdownRecoveryTransactionEnvironment();
>> +
>> +	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
>>  }
> 
> 
> What if we instead had a ProcGlobal->auxProc[auxproxtype]? We have different
> versions of this for different types auf aux processes, which doesn't really
> make sense.

I like that idea, but didn't try it yet.

>> +		 * Perform a plain atomic read first as a fast path for the case that
>> +		 * an interrupt is already pending.
>>  		 */
>> -		if (set->latch && !set->latch->is_set)
>> +		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
>> +		already_pending = ((old_mask & set->interrupt_mask) != 0);
>> +
>> +		if (!already_pending)
>>  		{
> 
> 
> Hm, I think this may be incorrect - what if there is *some* overlap between
> MyPendingInterrupts and set->interrupt_mask, but they're not equal?

I don't see the problem. In that case, already_pending will be set to 
'true', as it should.

>> +/*
>> + * Test an interrupt flag (of flags).
>> + */
>> +static inline bool
>> +IsInterruptPending(uint32 interruptMask)
>> +{
>> +	return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0;
>> +}
> 
> 
> Do we need to care about memory ordering?

Hmm, I think not. If you imagine using this with WaitInterrupt(), the 
WaitInterrupt works as a synchronization point. If the interrupt was 
just set by another procss, but this function returns a stale "false", 
the next WaitInterrupt() will return without sleeping. No other backend 
should be clearing our pending interrupt bits, so a stale "true" is not 
possible. I'll add a comment.

>> There's still a general-purpose INTERRUPT_GENERAL interrupt that is
>> multiplexed for many different purposes in different processes, for
>> example to wake up the walwriter when it has work to do, and to wake
>> up processes waiting on a condition variable. The common property of
>> those uses is that there's some other flag or condition that is
>> checked on each wakeup, the wakeup interrupt itself means merely that
>> something interesting might've happened.
> 
> I guess extensions that just need to use INTERRUPT_GENERAL, given that new
> interrupt reasons can't be dynamically registered?

Correct. I plan to implement a dynamic interrupt allocation mechanism 
for extensions, but it's not implemented yet.

(btw I renamed INTERRUPT_GENERAL to INTERRUPT_WAIT_WAKEUP)


>> +	if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
>>  	{
>>  		int			autovacuum_max_workers_prev = autovacuum_max_workers;
>>  
>> -		ConfigReloadPending = false;
>>  		ProcessConfigFile(PGC_SIGHUP);
>>  
>>  		/* shutdown requested in config file? */
>> @@ -771,11 +774,11 @@ ProcessAutoVacLauncherInterrupts(void)
>>  	}
>>  
>>  	/* Process barrier events */
>> -	if (ProcSignalBarrierPending)
>> +	if (IsInterruptPending(INTERRUPT_BARRIER))
>>  		ProcessProcSignalBarrier();
> 
> 
> Why do we have some code immediately consuming and others just checking if an
> interrupt is pending?

I was just sloppy. Now it's consistent: ProcessInterrupts() now always 
clears the interrupt before calling the handler function, and the 
handler function does not check or clear the interrupt.

>> @@ -572,10 +574,18 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
>>  			cur_timeout = Min(cur_timeout, XLogArchiveTimeout - elapsed_secs);
>>  		}
>>  
>> -		(void) WaitInterrupt(INTERRUPT_GENERAL,
>> -							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
>> -							 cur_timeout * 1000L /* convert to ms */ ,
>> -							 WAIT_EVENT_CHECKPOINTER_MAIN);
>> +		(void) WaitInterrupt(
>> +			/* these are handled in the main loop */
>> +			INTERRUPT_GENERAL | 	/* checkpoint requested */
> 
> 
> Why is a checkpoint request done via GENERAL? Seems that's specifically one
> that we do *not* want to absorb via CFI?

It works fine because there's a separate shared memory flag that's set 
when checkpoint is requested. We check that flag in the main loop, even 
if the interrupt is absorbed.

> 
>> @@ -947,6 +953,7 @@ void
>>  StandbyDeadLockHandler(void)
>>  {
>>  	got_standby_deadlock_timeout = true;
>> +	RaiseInterrupt(INTERRUPT_GENERAL);
>>  }
>>  
>>  /*
>> @@ -956,6 +963,7 @@ void
>>  StandbyTimeoutHandler(void)
>>  {
>>  	got_standby_delay_timeout = true;
>> +	RaiseInterrupt(INTERRUPT_GENERAL);
>>  }
>>  
>>  /*
>> @@ -965,6 +973,7 @@ void
>>  StandbyLockTimeoutHandler(void)
>>  {
>>  	got_standby_lock_timeout = true;
>> +	RaiseInterrupt(INTERRUPT_GENERAL);
>>  }
> 
> 
> Why are these using INTERRUPT_GENERAL?

Could be changed for sure, but this works too. These are quite localized.

>> +
>> +	if (ConsumeInterrupt(INTERRUPT_WALSND_INIT_STOPPING))
>> +		ProcessWalSndInitStopping();
> 
> 
> Huh. So ProcessWalSndInitStopping() raises different inerrupts to actually
> exit and thus relies on being called first? That seems rather clunky.

I don't know about the "being called first" part, but yeah this is 
clunky. I had hard time understanding the logic and I'm still not 100% I 
got it right to be honest. Some further cleanup for readability would be 
nice..

[1] 
https://www.postgresql.org/message-id/[email protected]

- Heikki

Attachments:

  [text/x-patch] v8-0001-Remove-useless-errdetail_abort.patch (11.9K, ../../[email protected]/2-v8-0001-Remove-useless-errdetail_abort.patch)
  download | inline diff:
From 1904acfcf7ec2181301a37c06d521e5171895bd9 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 22 Jan 2026 21:33:35 +0200
Subject: [PATCH v8 1/8] Remove useless errdetail_abort()

I don't understand how to reach errdetail_abort() with
MyProc->recoveryConflictPending set. If a recovery conflict signal is
received, ProcessRecoveryConflictInterrupt() raises an ERROR or FATAL
error to cancel the query or connection, and abort processing clears
the flag. The error message from ProcessRecoveryConflictInterrupt() is
very clear that the query or connection was terminated because of
recovery conflict.

The only way to reach it AFAICS is with a race condition, if the
startup process sends a recovery conflict signal when the transaction
has just entered aborted state for some other reason. And in that case
the detail would be misleading, as the transaction was already aborted
for some other reason, not because of the recovery conflict.

errdetail_abort() was the only user of the recoveryConflictPending
flag in PGPROC, so we can remove that and all the related code too.
---
 src/backend/storage/ipc/procarray.c | 20 +++--------------
 src/backend/storage/ipc/standby.c   | 15 ++++++-------
 src/backend/storage/lmgr/proc.c     |  1 -
 src/backend/tcop/postgres.c         | 35 +++++------------------------
 src/include/storage/proc.h          |  7 ------
 src/include/storage/procarray.h     |  6 ++---
 6 files changed, 18 insertions(+), 66 deletions(-)

diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 6be565155ab..748c06b51cb 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -708,8 +708,6 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 		/* be sure this is cleared in abort */
 		proc->delayChkptFlags = 0;
 
-		proc->recoveryConflictPending = false;
-
 		/* must be cleared with xid/xmin: */
 		/* avoid unnecessarily dirtying shared cachelines */
 		if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
@@ -750,8 +748,6 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
 	/* be sure this is cleared in abort */
 	proc->delayChkptFlags = 0;
 
-	proc->recoveryConflictPending = false;
-
 	/* must be cleared with xid/xmin: */
 	/* avoid unnecessarily dirtying shared cachelines */
 	if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
@@ -933,7 +929,6 @@ ProcArrayClearTransaction(PGPROC *proc)
 
 	proc->vxid.lxid = InvalidLocalTransactionId;
 	proc->xmin = InvalidTransactionId;
-	proc->recoveryConflictPending = false;
 
 	Assert(!(proc->statusFlags & PROC_VACUUM_STATE_MASK));
 	Assert(!proc->delayChkptFlags);
@@ -3445,19 +3440,12 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
 }
 
 /*
- * CancelVirtualTransaction - used in recovery conflict processing
+ * SignalVirtualTransaction - used in recovery conflict processing
  *
  * Returns pid of the process signaled, or 0 if not found.
  */
 pid_t
-CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
-{
-	return SignalVirtualTransaction(vxid, sigmode, true);
-}
-
-pid_t
-SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
-						 bool conflictPending)
+SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
 {
 	ProcArrayStruct *arrayP = procArray;
 	int			index;
@@ -3476,7 +3464,6 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
 		if (procvxid.procNumber == vxid.procNumber &&
 			procvxid.localTransactionId == vxid.localTransactionId)
 		{
-			proc->recoveryConflictPending = conflictPending;
 			pid = proc->pid;
 			if (pid != 0)
 			{
@@ -3618,7 +3605,7 @@ CountDBConnections(Oid databaseid)
  * CancelDBBackends --- cancel backends that are using specified database
  */
 void
-CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
+CancelDBBackends(Oid databaseid, ProcSignalReason sigmode)
 {
 	ProcArrayStruct *arrayP = procArray;
 	int			index;
@@ -3638,7 +3625,6 @@ CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending)
 
 			GET_VXID_FROM_PGPROC(procvxid, *proc);
 
-			proc->recoveryConflictPending = conflictPending;
 			pid = proc->pid;
 			if (pid != 0)
 			{
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index afffab77106..6db803476c4 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -390,7 +390,7 @@ ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
 				 * Now find out who to throw out of the balloon.
 				 */
 				Assert(VirtualTransactionIdIsValid(*waitlist));
-				pid = CancelVirtualTransaction(*waitlist, reason);
+				pid = SignalVirtualTransaction(*waitlist, reason);
 
 				/*
 				 * Wait a little bit for it to die so that we avoid flooding
@@ -581,7 +581,7 @@ ResolveRecoveryConflictWithDatabase(Oid dbid)
 	 */
 	while (CountDBBackends(dbid) > 0)
 	{
-		CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE, true);
+		CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE);
 
 		/*
 		 * Wait awhile for them to die so that we avoid flooding an
@@ -724,8 +724,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		while (VirtualTransactionIdIsValid(*backends))
 		{
 			SignalVirtualTransaction(*backends,
-									 PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
-									 false);
+									 PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 			backends++;
 		}
 
@@ -881,11 +880,11 @@ SendRecoveryConflictWithBufferPin(ProcSignalReason reason)
 
 	/*
 	 * We send signal to all backends to ask them if they are holding the
-	 * buffer pin which is delaying the Startup process. We must not set the
-	 * conflict flag yet, since most backends will be innocent. Let the
-	 * SIGUSR1 handling in each backend decide their own fate.
+	 * buffer pin which is delaying the Startup process. Most of them will be
+	 * innocent, but we let the SIGUSR1 handling in each backend decide their
+	 * own fate.
 	 */
-	CancelDBBackends(InvalidOid, reason, false);
+	CancelDBBackends(InvalidOid, reason);
 }
 
 /*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 063826ae576..6455bb6e01f 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -506,7 +506,6 @@ InitProcess(void)
 			Assert(dlist_is_empty(&(MyProc->myProcLocks[i])));
 	}
 #endif
-	MyProc->recoveryConflictPending = false;
 
 	/* Initialize fields for sync rep */
 	MyProc->waitLSN = 0;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index e54bf1e760f..29becf0a703 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -175,7 +175,6 @@ static void forbidden_in_wal_sender(char firstchar);
 static bool check_log_statement(List *stmt_list);
 static int	errdetail_execute(List *raw_parsetree_list);
 static int	errdetail_params(ParamListInfo params);
-static int	errdetail_abort(void);
 static void bind_param_error_callback(void *arg);
 static void start_xact_command(void);
 static void finish_xact_command(void);
@@ -1141,8 +1140,7 @@ exec_simple_query(const char *query_string)
 			ereport(ERROR,
 					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 					 errmsg("current transaction is aborted, "
-							"commands ignored until end of transaction block"),
-					 errdetail_abort()));
+							"commands ignored until end of transaction block")));
 
 		/* Make sure we are in a transaction command */
 		start_xact_command();
@@ -1498,8 +1496,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 			ereport(ERROR,
 					(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 					 errmsg("current transaction is aborted, "
-							"commands ignored until end of transaction block"),
-					 errdetail_abort()));
+							"commands ignored until end of transaction block")));
 
 		/*
 		 * Create the CachedPlanSource before we do parse analysis, since it
@@ -1750,8 +1747,7 @@ exec_bind_message(StringInfo input_message)
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 				 errmsg("current transaction is aborted, "
-						"commands ignored until end of transaction block"),
-				 errdetail_abort()));
+						"commands ignored until end of transaction block")));
 
 	/*
 	 * Create the portal.  Allow silent replacement of an existing portal only
@@ -2255,8 +2251,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 				 errmsg("current transaction is aborted, "
-						"commands ignored until end of transaction block"),
-				 errdetail_abort()));
+						"commands ignored until end of transaction block")));
 
 	/* Check for cancel signal before we start execution */
 	CHECK_FOR_INTERRUPTS();
@@ -2536,20 +2531,6 @@ errdetail_params(ParamListInfo params)
 	return 0;
 }
 
-/*
- * errdetail_abort
- *
- * Add an errdetail() line showing abort reason, if any.
- */
-static int
-errdetail_abort(void)
-{
-	if (MyProc->recoveryConflictPending)
-		errdetail("Abort reason: recovery conflict");
-
-	return 0;
-}
-
 /*
  * errdetail_recovery_conflict
  *
@@ -2692,8 +2673,7 @@ exec_describe_statement_message(const char *stmt_name)
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 				 errmsg("current transaction is aborted, "
-						"commands ignored until end of transaction block"),
-				 errdetail_abort()));
+						"commands ignored until end of transaction block")));
 
 	if (whereToSendOutput != DestRemote)
 		return;					/* can't actually do anything... */
@@ -2769,8 +2749,7 @@ exec_describe_portal_message(const char *portal_name)
 		ereport(ERROR,
 				(errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION),
 				 errmsg("current transaction is aborted, "
-						"commands ignored until end of transaction block"),
-				 errdetail_abort()));
+						"commands ignored until end of transaction block")));
 
 	if (whereToSendOutput != DestRemote)
 		return;					/* can't actually do anything... */
@@ -3139,8 +3118,6 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 				return;
 			}
 
-			MyProc->recoveryConflictPending = true;
-
 			/* Intentional fall through to error handling */
 			/* FALLTHROUGH */
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 039bc8353be..81f1960a635 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -235,13 +235,6 @@ struct PGPROC
 
 	bool		isRegularBackend;	/* true if it's a regular backend. */
 
-	/*
-	 * While in hot standby mode, shows that a conflict signal has been sent
-	 * for the current transaction. Set/cleared while holding ProcArrayLock,
-	 * though not required. Accessed without lock, if needed.
-	 */
-	bool		recoveryConflictPending;
-
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da7b5e78d30..3a8593f87ba 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -77,14 +77,12 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   bool excludeXmin0, bool allDbs, int excludeVacuum,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
-extern pid_t CancelVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode);
-extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode,
-									  bool conflictPending);
+extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode);
 
 extern bool MinimumActiveBackends(int min);
 extern int	CountDBBackends(Oid databaseid);
 extern int	CountDBConnections(Oid databaseid);
-extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode, bool conflictPending);
+extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode);
 extern int	CountUserBackends(Oid roleid);
 extern bool CountOtherDBBackends(Oid databaseId,
 								 int *nbackends, int *nprepared);
-- 
2.47.3



  [text/x-patch] v8-0002-Don-t-hint-that-you-can-reconnect-when-the-databa.patch (2.0K, ../../[email protected]/3-v8-0002-Don-t-hint-that-you-can-reconnect-when-the-databa.patch)
  download | inline diff:
From 202c07dae041d465e4de71e369940b09b0a2f380 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 22 Jan 2026 19:17:30 +0200
Subject: [PATCH v8 2/8] Don't hint that you can reconnect when the database is
 dropped

---
 src/backend/tcop/postgres.c | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 29becf0a703..980ef1da100 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3209,27 +3209,29 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 				}
 			}
 
-			/* Intentional fall through to session cancel */
-			/* FALLTHROUGH */
-
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
-
 			/*
-			 * Retrying is not possible because the database is dropped, or we
-			 * decided above that we couldn't resolve the conflict with an
-			 * ERROR and fell through.  Terminate the session.
+			 * The conflict cannot be resolved with ERROR, so terminate the
+			 * whole session.
 			 */
 			pgstat_report_recovery_conflict(reason);
 			ereport(FATAL,
-					(errcode(reason == PROCSIG_RECOVERY_CONFLICT_DATABASE ?
-							 ERRCODE_DATABASE_DROPPED :
-							 ERRCODE_T_R_SERIALIZATION_FAILURE),
+					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
 					 errmsg("terminating connection due to conflict with recovery"),
 					 errdetail_recovery_conflict(reason),
 					 errhint("In a moment you should be able to reconnect to the"
 							 " database and repeat your command.")));
 			break;
 
+		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+
+			/* The database is being dropped; terminate the session */
+			pgstat_report_recovery_conflict(reason);
+			ereport(FATAL,
+					(errcode(ERRCODE_DATABASE_DROPPED),
+					 errmsg("terminating connection due to conflict with recovery"),
+					 errdetail_recovery_conflict(reason)));
+			break;
+
 		default:
 			elog(FATAL, "unrecognized conflict mode: %d", (int) reason);
 	}
-- 
2.47.3



  [text/x-patch] v8-0003-Use-ProcNumber-rather-than-pid-in-ReplicationSlot.patch (11.1K, ../../[email protected]/4-v8-0003-Use-ProcNumber-rather-than-pid-in-ReplicationSlot.patch)
  download | inline diff:
From d2c89a000bf10af254090bd24d6acbb098bc4f8e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 22 Jan 2026 14:25:39 +0200
Subject: [PATCH v8 3/8] Use ProcNumber rather than pid in ReplicationSlot

This helps the next commit
---
 src/backend/replication/logical/slotsync.c |  2 +-
 src/backend/replication/slot.c             | 71 ++++++++++++----------
 src/backend/replication/slotfuncs.c        | 13 ++--
 src/include/replication/slot.h             |  7 ++-
 4 files changed, 53 insertions(+), 40 deletions(-)

diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 1c343d03d21..60fc756b509 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1759,7 +1759,7 @@ update_synced_slots_inactive_since(void)
 			Assert(SlotIsLogical(s));
 
 			/* The slot must not be acquired by any process */
-			Assert(s->active_pid == 0);
+			Assert(s->active_proc == INVALID_PROC_NUMBER);
 
 			/* Use the same inactive_since time for all the slots. */
 			if (now == 0)
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 4c47261c7f9..5c6f704e029 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -226,6 +226,7 @@ ReplicationSlotsShmemInit(void)
 			ReplicationSlot *slot = &ReplicationSlotCtl->replication_slots[i];
 
 			/* everything else is zeroed by the memset above */
+			slot->active_proc = INVALID_PROC_NUMBER;
 			SpinLockInit(&slot->mutex);
 			LWLockInitialize(&slot->io_in_progress_lock,
 							 LWTRANCHE_REPLICATION_SLOT_IO);
@@ -461,7 +462,7 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 	 * be doing that.  So it's safe to initialize the slot.
 	 */
 	Assert(!slot->in_use);
-	Assert(slot->active_pid == 0);
+	Assert(slot->active_proc == INVALID_PROC_NUMBER);
 
 	/* first initialize persistent data */
 	memset(&slot->data, 0, sizeof(ReplicationSlotPersistentData));
@@ -505,8 +506,8 @@ ReplicationSlotCreate(const char *name, bool db_specific,
 
 	/* We can now mark the slot active, and that makes it our slot. */
 	SpinLockAcquire(&slot->mutex);
-	Assert(slot->active_pid == 0);
-	slot->active_pid = MyProcPid;
+	Assert(slot->active_proc == INVALID_PROC_NUMBER);
+	slot->active_proc = MyProcNumber;
 	SpinLockRelease(&slot->mutex);
 	MyReplicationSlot = slot;
 
@@ -620,7 +621,8 @@ void
 ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
-	int			active_pid;
+	ProcNumber	active_proc;
+	pid_t		active_pid;
 
 	Assert(name != NULL);
 
@@ -672,17 +674,18 @@ retry:
 		 * to inactive_since in InvalidatePossiblyObsoleteSlot.
 		 */
 		SpinLockAcquire(&s->mutex);
-		if (s->active_pid == 0)
-			s->active_pid = MyProcPid;
-		active_pid = s->active_pid;
+		if (s->active_proc == INVALID_PROC_NUMBER)
+			s->active_proc = MyProcNumber;
+		active_proc = s->active_proc;
 		ReplicationSlotSetInactiveSince(s, 0, false);
 		SpinLockRelease(&s->mutex);
 	}
 	else
 	{
-		s->active_pid = active_pid = MyProcPid;
+		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
+	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -690,7 +693,7 @@ retry:
 	 * wait until the owning process signals us that it's been released, or
 	 * error out.
 	 */
-	if (active_pid != MyProcPid)
+	if (active_proc != MyProcNumber)
 	{
 		if (!nowait)
 		{
@@ -762,7 +765,7 @@ ReplicationSlotRelease(void)
 	bool		is_logical;
 	TimestampTz now = 0;
 
-	Assert(slot != NULL && slot->active_pid != 0);
+	Assert(slot != NULL && slot->active_proc != INVALID_PROC_NUMBER);
 
 	is_logical = SlotIsLogical(slot);
 
@@ -815,7 +818,7 @@ ReplicationSlotRelease(void)
 		 * disconnecting, but wake up others that may be waiting for it.
 		 */
 		SpinLockAcquire(&slot->mutex);
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 		ReplicationSlotSetInactiveSince(slot, now, false);
 		SpinLockRelease(&slot->mutex);
 		ConditionVariableBroadcast(&slot->active_cv);
@@ -877,7 +880,7 @@ restart:
 		found_valid_logicalslot |=
 			(SlotIsLogical(s) && s->data.invalidated == RS_INVAL_NONE);
 
-		if ((s->active_pid == MyProcPid &&
+		if ((s->active_proc == MyProcNumber &&
 			 (!synced_only || s->data.synced)))
 		{
 			Assert(s->data.persistency == RS_TEMPORARY);
@@ -1088,7 +1091,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot)
 		bool		fail_softly = slot->data.persistency != RS_PERSISTENT;
 
 		SpinLockAcquire(&slot->mutex);
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 		SpinLockRelease(&slot->mutex);
 
 		/* wake up anyone waiting on this slot */
@@ -1110,7 +1113,7 @@ ReplicationSlotDropPtr(ReplicationSlot *slot)
 	 * Also wake up processes waiting for it.
 	 */
 	LWLockAcquire(ReplicationSlotControlLock, LW_EXCLUSIVE);
-	slot->active_pid = 0;
+	slot->active_proc = INVALID_PROC_NUMBER;
 	slot->in_use = false;
 	LWLockRelease(ReplicationSlotControlLock);
 	ConditionVariableBroadcast(&slot->active_cv);
@@ -1476,7 +1479,7 @@ ReplicationSlotsCountDBSlots(Oid dboid, int *nslots, int *nactive)
 		/* count slots with spinlock held */
 		SpinLockAcquire(&s->mutex);
 		(*nslots)++;
-		if (s->active_pid != 0)
+		if (s->active_proc != INVALID_PROC_NUMBER)
 			(*nactive)++;
 		SpinLockRelease(&s->mutex);
 	}
@@ -1520,7 +1523,7 @@ restart:
 	{
 		ReplicationSlot *s;
 		char	   *slotname;
-		int			active_pid;
+		ProcNumber	active_proc;
 
 		s = &ReplicationSlotCtl->replication_slots[i];
 
@@ -1550,11 +1553,11 @@ restart:
 		SpinLockAcquire(&s->mutex);
 		/* can't change while ReplicationSlotControlLock is held */
 		slotname = NameStr(s->data.name);
-		active_pid = s->active_pid;
-		if (active_pid == 0)
+		active_proc = s->active_proc;
+		if (active_proc == INVALID_PROC_NUMBER)
 		{
 			MyReplicationSlot = s;
-			s->active_pid = MyProcPid;
+			s->active_proc = MyProcNumber;
 		}
 		SpinLockRelease(&s->mutex);
 
@@ -1579,11 +1582,11 @@ restart:
 		 * XXX: We can consider shutting down the slot sync worker before
 		 * trying to drop synced temporary slots here.
 		 */
-		if (active_pid)
+		if (active_proc != INVALID_PROC_NUMBER)
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_IN_USE),
 					 errmsg("replication slot \"%s\" is active for PID %d",
-							slotname, active_pid)));
+							slotname, GetPGProcByNumber(active_proc)->pid)));
 
 		/*
 		 * To avoid duplicating ReplicationSlotDropAcquired() and to avoid
@@ -1974,7 +1977,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 	{
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
-		int			active_pid = 0;
+		ProcNumber	active_proc;
+		pid_t		active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2027,7 +2031,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		}
 
 		slotname = s->data.name;
-		active_pid = s->active_pid;
+		active_proc = s->active_proc;
 
 		/*
 		 * If the slot can be acquired, do so and mark it invalidated
@@ -2039,10 +2043,10 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		 * is terminated. So, the inactive slot can only be invalidated
 		 * immediately without being terminated.
 		 */
-		if (active_pid == 0)
+		if (active_proc == INVALID_PROC_NUMBER)
 		{
 			MyReplicationSlot = s;
-			s->active_pid = MyProcPid;
+			s->active_proc = MyProcNumber;
 			s->data.invalidated = invalidation_cause;
 
 			/*
@@ -2058,6 +2062,11 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
+		else
+		{
+			active_pid = GetPGProcByNumber(active_proc)->pid;
+			Assert(active_pid != 0);
+		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2073,7 +2082,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 								&slot_idle_usecs);
 		}
 
-		if (active_pid != 0)
+		if (active_proc != INVALID_PROC_NUMBER)
 		{
 			/*
 			 * Prepare the sleep on the slot's condition variable before
@@ -2105,9 +2114,9 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
-					(void) SendProcSignal(active_pid,
-										  PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
-										  INVALID_PROC_NUMBER);
+					SendProcSignal(active_pid,
+								   PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
+								   active_proc);
 				else
 					(void) kill(active_pid, SIGTERM);
 
@@ -2875,7 +2884,7 @@ RestoreSlotFromDisk(const char *name)
 		slot->candidate_restart_valid = InvalidXLogRecPtr;
 
 		slot->in_use = true;
-		slot->active_pid = 0;
+		slot->active_proc = INVALID_PROC_NUMBER;
 
 		/*
 		 * Set the time since the slot has become inactive after loading the
@@ -3158,7 +3167,7 @@ StandbySlotsHaveCaughtup(XLogRecPtr wait_for_lsn, int elevel)
 		SpinLockAcquire(&slot->mutex);
 		restart_lsn = slot->data.restart_lsn;
 		invalidated = slot->data.invalidated != RS_INVAL_NONE;
-		inactive = slot->active_pid == 0;
+		inactive = slot->active_proc == INVALID_PROC_NUMBER;
 		SpinLockRelease(&slot->mutex);
 
 		if (invalidated)
diff --git a/src/backend/replication/slotfuncs.c b/src/backend/replication/slotfuncs.c
index 1ed2d80c2d2..9f5e4f998fe 100644
--- a/src/backend/replication/slotfuncs.c
+++ b/src/backend/replication/slotfuncs.c
@@ -20,6 +20,7 @@
 #include "replication/logical.h"
 #include "replication/slot.h"
 #include "replication/slotsync.h"
+#include "storage/proc.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/pg_lsn.h"
@@ -309,10 +310,10 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 			values[i++] = ObjectIdGetDatum(slot_contents.data.database);
 
 		values[i++] = BoolGetDatum(slot_contents.data.persistency == RS_TEMPORARY);
-		values[i++] = BoolGetDatum(slot_contents.active_pid != 0);
+		values[i++] = BoolGetDatum(slot_contents.active_proc != INVALID_PROC_NUMBER);
 
-		if (slot_contents.active_pid != 0)
-			values[i++] = Int32GetDatum(slot_contents.active_pid);
+		if (slot_contents.active_proc != INVALID_PROC_NUMBER)
+			values[i++] = Int32GetDatum(GetPGProcByNumber(slot_contents.active_proc)->pid);
 		else
 			nulls[i++] = true;
 
@@ -377,13 +378,13 @@ pg_get_replication_slots(PG_FUNCTION_ARGS)
 				 */
 				if (XLogRecPtrIsValid(slot_contents.data.restart_lsn))
 				{
-					int			pid;
+					ProcNumber	procno;
 
 					SpinLockAcquire(&slot->mutex);
-					pid = slot->active_pid;
+					procno = slot->active_proc;
 					slot_contents.data.restart_lsn = slot->data.restart_lsn;
 					SpinLockRelease(&slot->mutex);
-					if (pid != 0)
+					if (procno != INVALID_PROC_NUMBER)
 					{
 						values[i++] = CStringGetTextDatum("unreserved");
 						walstate = WALAVAIL_UNRESERVED;
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index f465e430cc6..72f8be629f3 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -185,8 +185,11 @@ typedef struct ReplicationSlot
 	/* is this slot defined */
 	bool		in_use;
 
-	/* Who is streaming out changes for this slot? 0 in unused slots. */
-	pid_t		active_pid;
+	/*
+	 * Who is streaming out changes for this slot? INVALID_PROC_NUMBER in
+	 * unused slots.
+	 */
+	ProcNumber	active_proc;
 
 	/* any outstanding modifications? */
 	bool		just_dirtied;
-- 
2.47.3



  [text/x-patch] v8-0004-Separate-RecoveryConflictReasons-from-procsignals.patch (37.7K, ../../[email protected]/5-v8-0004-Separate-RecoveryConflictReasons-from-procsignals.patch)
  download | inline diff:
From 56cece6ed9e60b99115d0afc2c271b0fcb46ea04 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 23 Jan 2026 00:49:11 +0200
Subject: [PATCH v8 4/8] Separate RecoveryConflictReasons from procsignals

Share the same PROCSIG_RECOVERY_CONFLICT flag for all recovery
conflict reasons. To distinguish, have a bitmask in PGPROC to indicate
the reason(s).
---
 src/backend/commands/dbcommands.c            |   1 +
 src/backend/commands/tablespace.c            |   1 +
 src/backend/replication/logical/logicalctl.c |   1 +
 src/backend/replication/slot.c               |   6 +-
 src/backend/storage/buffer/bufmgr.c          |   5 +-
 src/backend/storage/ipc/procarray.c          | 136 +++++++++++++------
 src/backend/storage/ipc/procsignal.c         |  22 +--
 src/backend/storage/ipc/standby.c            |  61 ++++-----
 src/backend/storage/lmgr/proc.c              |   5 +-
 src/backend/tcop/postgres.c                  | 109 ++++++++-------
 src/backend/utils/activity/pgstat_database.c |  18 +--
 src/backend/utils/adt/mcxtfuncs.c            |   1 +
 src/include/storage/proc.h                   |   9 ++
 src/include/storage/procarray.h              |   7 +-
 src/include/storage/procsignal.h             |  16 +--
 src/include/storage/standby.h                |  34 ++++-
 src/include/tcop/tcopprot.h                  |   2 +-
 src/tools/pgindent/typedefs.list             |   1 +
 18 files changed, 250 insertions(+), 185 deletions(-)

diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 87949054f26..33311760df7 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -60,6 +60,7 @@
 #include "storage/lmgr.h"
 #include "storage/md.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index 0b064891932..3511a4ec0fd 100644
--- a/src/backend/commands/tablespace.c
+++ b/src/backend/commands/tablespace.c
@@ -70,6 +70,7 @@
 #include "miscadmin.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
+#include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
diff --git a/src/backend/replication/logical/logicalctl.c b/src/backend/replication/logical/logicalctl.c
index 9f787f3dc51..4e292951201 100644
--- a/src/backend/replication/logical/logicalctl.c
+++ b/src/backend/replication/logical/logicalctl.c
@@ -71,6 +71,7 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "utils/injection_point.h"
 
 /*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 5c6f704e029..143569dffe3 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -2114,9 +2114,9 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
-					SendProcSignal(active_pid,
-								   PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
-								   active_proc);
+					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
+												  active_pid,
+												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
 					(void) kill(active_pid, SIGTERM);
 
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 6f935648ae9..3bd86223abd 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -59,6 +59,7 @@
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/proclist.h"
+#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "storage/standby.h"
@@ -6560,7 +6561,7 @@ LockBufferForCleanup(Buffer buffer)
 			 * deadlock_timeout for it.
 			 */
 			if (logged_recovery_conflict)
-				LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
+				LogRecoveryConflict(RECOVERY_CONFLICT_BUFFERPIN,
 									waitStart, GetCurrentTimestamp(),
 									NULL, false);
 
@@ -6611,7 +6612,7 @@ LockBufferForCleanup(Buffer buffer)
 				if (TimestampDifferenceExceeds(waitStart, now,
 											   DeadlockTimeout))
 				{
-					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
+					LogRecoveryConflict(RECOVERY_CONFLICT_BUFFERPIN,
 										waitStart, now, NULL, true);
 					logged_recovery_conflict = true;
 				}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 748c06b51cb..423ca3aeaa1 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -60,6 +60,7 @@
 #include "port/pg_lfind.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/injection_point.h"
@@ -708,6 +709,8 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
 		/* be sure this is cleared in abort */
 		proc->delayChkptFlags = 0;
 
+		pg_atomic_write_u32(&proc->pendingRecoveryConflicts, 0);
+
 		/* must be cleared with xid/xmin: */
 		/* avoid unnecessarily dirtying shared cachelines */
 		if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
@@ -748,6 +751,8 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
 	/* be sure this is cleared in abort */
 	proc->delayChkptFlags = 0;
 
+	pg_atomic_write_u32(&proc->pendingRecoveryConflicts, 0);
+
 	/* must be cleared with xid/xmin: */
 	/* avoid unnecessarily dirtying shared cachelines */
 	if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
@@ -929,6 +934,7 @@ ProcArrayClearTransaction(PGPROC *proc)
 
 	proc->vxid.lxid = InvalidLocalTransactionId;
 	proc->xmin = InvalidTransactionId;
+	pg_atomic_write_u32(&proc->pendingRecoveryConflicts, 0);
 
 	Assert(!(proc->statusFlags & PROC_VACUUM_STATE_MASK));
 	Assert(!proc->delayChkptFlags);
@@ -3440,12 +3446,46 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
 }
 
 /*
- * SignalVirtualTransaction - used in recovery conflict processing
+ * SignalRecoveryConflict -- signal that a process is blocking recovery
  *
- * Returns pid of the process signaled, or 0 if not found.
+ * The 'pid' is redundant with 'proc', but it acts as a cross-check to
+ * detect process had exited and the PGPROC entry was reused for a different
+ * process.
+ *
+ * Returns true if the process was signaled, or false if not found.
  */
-pid_t
-SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
+bool
+SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+{
+	bool		found = false;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+
+	/*
+	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * ignore any errors.
+	 */
+	if (proc->pid == pid)
+	{
+		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
+
+		/* wake up the process */
+		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		found = true;
+	}
+
+	LWLockRelease(ProcArrayLock);
+
+	return found;
+}
+
+/*
+ * SignalRecoveryConflictWithVirtualXID -- signal that a VXID is blocking recovery
+ *
+ * Like SignalRecoveryConflict, but the target is identified by VXID
+ */
+bool
+SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason)
 {
 	ProcArrayStruct *arrayP = procArray;
 	int			index;
@@ -3467,11 +3507,13 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
 			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
 				 * wanted so ignore any errors.
 				 */
-				(void) SendProcSignal(pid, sigmode, vxid.procNumber);
+				(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, vxid.procNumber);
 			}
 			break;
 		}
@@ -3479,7 +3521,50 @@ SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode)
 
 	LWLockRelease(ProcArrayLock);
 
-	return pid;
+	return pid != 0;
+}
+
+/*
+ * SignalRecoveryConflictWithDatabase --- signal all backends specified database
+ *
+ * Like SignalRecoveryConflict, but signals all backends using the database.
+ */
+void
+SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason)
+{
+	ProcArrayStruct *arrayP = procArray;
+	int			index;
+
+	/* tell all backends to die */
+	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		PGPROC	   *proc = &allProcs[pgprocno];
+
+		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
+				 * wanted so ignore any errors.
+				 */
+				(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, procvxid.procNumber);
+			}
+		}
+	}
+
+	LWLockRelease(ProcArrayLock);
 }
 
 /*
@@ -3601,45 +3686,6 @@ CountDBConnections(Oid databaseid)
 	return count;
 }
 
-/*
- * CancelDBBackends --- cancel backends that are using specified database
- */
-void
-CancelDBBackends(Oid databaseid, ProcSignalReason sigmode)
-{
-	ProcArrayStruct *arrayP = procArray;
-	int			index;
-
-	/* tell all backends to die */
-	LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
-
-	for (index = 0; index < arrayP->numProcs; index++)
-	{
-		int			pgprocno = arrayP->pgprocnos[index];
-		PGPROC	   *proc = &allProcs[pgprocno];
-
-		if (databaseid == InvalidOid || proc->databaseId == databaseid)
-		{
-			VirtualTransactionId procvxid;
-			pid_t		pid;
-
-			GET_VXID_FROM_PGPROC(procvxid, *proc);
-
-			pid = proc->pid;
-			if (pid != 0)
-			{
-				/*
-				 * Kill the pid if it's still here. If not, that's what we
-				 * wanted so ignore any errors.
-				 */
-				(void) SendProcSignal(pid, sigmode, procvxid.procNumber);
-			}
-		}
-	}
-
-	LWLockRelease(ProcArrayLock);
-}
-
 /*
  * CountUserBackends --- count backends that are used by specified user
  * (only regular backends, not any type of background worker)
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 8e56922dcea..5d33559926a 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -697,26 +697,8 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
 		HandleParallelApplyMessageInterrupt();
 
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_DATABASE))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_DATABASE);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_TABLESPACE))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_TABLESPACE);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOCK))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOCK);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_SNAPSHOT);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN))
-		HandleRecoveryConflictInterrupt(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
+		HandleRecoveryConflictInterrupt();
 
 	SetLatch(MyLatch);
 }
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 6db803476c4..0851789e8b6 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -71,13 +71,13 @@ static volatile sig_atomic_t got_standby_delay_timeout = false;
 static volatile sig_atomic_t got_standby_lock_timeout = false;
 
 static void ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
-												   ProcSignalReason reason,
+												   RecoveryConflictReason reason,
 												   uint32 wait_event_info,
 												   bool report_waiting);
-static void SendRecoveryConflictWithBufferPin(ProcSignalReason reason);
+static void SendRecoveryConflictWithBufferPin(RecoveryConflictReason reason);
 static XLogRecPtr LogCurrentRunningXacts(RunningTransactions CurrRunningXacts);
 static void LogAccessExclusiveLocks(int nlocks, xl_standby_lock *locks);
-static const char *get_recovery_conflict_desc(ProcSignalReason reason);
+static const char *get_recovery_conflict_desc(RecoveryConflictReason reason);
 
 /*
  * InitRecoveryTransactionEnvironment
@@ -271,7 +271,7 @@ WaitExceedsMaxStandbyDelay(uint32 wait_event_info)
  * to be resolved or not.
  */
 void
-LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
+LogRecoveryConflict(RecoveryConflictReason reason, TimestampTz wait_start,
 					TimestampTz now, VirtualTransactionId *wait_list,
 					bool still_waiting)
 {
@@ -358,7 +358,8 @@ LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
  */
 static void
 ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
-									   ProcSignalReason reason, uint32 wait_event_info,
+									   RecoveryConflictReason reason,
+									   uint32 wait_event_info,
 									   bool report_waiting)
 {
 	TimestampTz waitStart = 0;
@@ -384,19 +385,19 @@ ResolveRecoveryConflictWithVirtualXIDs(VirtualTransactionId *waitlist,
 			/* Is it time to kill it? */
 			if (WaitExceedsMaxStandbyDelay(wait_event_info))
 			{
-				pid_t		pid;
+				bool		signaled;
 
 				/*
 				 * Now find out who to throw out of the balloon.
 				 */
 				Assert(VirtualTransactionIdIsValid(*waitlist));
-				pid = SignalVirtualTransaction(*waitlist, reason);
+				signaled = SignalRecoveryConflictWithVirtualXID(*waitlist, reason);
 
 				/*
 				 * Wait a little bit for it to die so that we avoid flooding
 				 * an unresponsive backend when system is heavily loaded.
 				 */
-				if (pid != 0)
+				if (signaled)
 					pg_usleep(5000L);
 			}
 
@@ -489,7 +490,7 @@ ResolveRecoveryConflictWithSnapshot(TransactionId snapshotConflictHorizon,
 	backends = GetConflictingVirtualXIDs(snapshotConflictHorizon,
 										 locator.dbOid);
 	ResolveRecoveryConflictWithVirtualXIDs(backends,
-										   PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
+										   RECOVERY_CONFLICT_SNAPSHOT,
 										   WAIT_EVENT_RECOVERY_CONFLICT_SNAPSHOT,
 										   true);
 
@@ -560,7 +561,7 @@ ResolveRecoveryConflictWithTablespace(Oid tsid)
 	temp_file_users = GetConflictingVirtualXIDs(InvalidTransactionId,
 												InvalidOid);
 	ResolveRecoveryConflictWithVirtualXIDs(temp_file_users,
-										   PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
+										   RECOVERY_CONFLICT_TABLESPACE,
 										   WAIT_EVENT_RECOVERY_CONFLICT_TABLESPACE,
 										   true);
 }
@@ -581,7 +582,7 @@ ResolveRecoveryConflictWithDatabase(Oid dbid)
 	 */
 	while (CountDBBackends(dbid) > 0)
 	{
-		CancelDBBackends(dbid, PROCSIG_RECOVERY_CONFLICT_DATABASE);
+		SignalRecoveryConflictWithDatabase(dbid, RECOVERY_CONFLICT_DATABASE);
 
 		/*
 		 * Wait awhile for them to die so that we avoid flooding an
@@ -665,7 +666,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		 * because the caller, WaitOnLock(), has already reported that.
 		 */
 		ResolveRecoveryConflictWithVirtualXIDs(backends,
-											   PROCSIG_RECOVERY_CONFLICT_LOCK,
+											   RECOVERY_CONFLICT_LOCK,
 											   PG_WAIT_LOCK | locktag.locktag_type,
 											   false);
 	}
@@ -723,8 +724,8 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag, bool logging_conflict)
 		 */
 		while (VirtualTransactionIdIsValid(*backends))
 		{
-			SignalVirtualTransaction(*backends,
-									 PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+			(void) SignalRecoveryConflictWithVirtualXID(*backends,
+														RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 			backends++;
 		}
 
@@ -802,7 +803,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 		/*
 		 * We're already behind, so clear a path as quickly as possible.
 		 */
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN);
 	}
 	else
 	{
@@ -842,7 +843,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 	ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
 
 	if (got_standby_delay_timeout)
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_BUFFERPIN);
+		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN);
 	else if (got_standby_deadlock_timeout)
 	{
 		/*
@@ -858,7 +859,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 		 * not be so harmful because the period that the buffer is kept pinned
 		 * is basically no so long. But we should fix this?
 		 */
-		SendRecoveryConflictWithBufferPin(PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 	}
 
 	/*
@@ -873,10 +874,10 @@ ResolveRecoveryConflictWithBufferPin(void)
 }
 
 static void
-SendRecoveryConflictWithBufferPin(ProcSignalReason reason)
+SendRecoveryConflictWithBufferPin(RecoveryConflictReason reason)
 {
-	Assert(reason == PROCSIG_RECOVERY_CONFLICT_BUFFERPIN ||
-		   reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+	Assert(reason == RECOVERY_CONFLICT_BUFFERPIN ||
+		   reason == RECOVERY_CONFLICT_STARTUP_DEADLOCK);
 
 	/*
 	 * We send signal to all backends to ask them if they are holding the
@@ -884,7 +885,7 @@ SendRecoveryConflictWithBufferPin(ProcSignalReason reason)
 	 * innocent, but we let the SIGUSR1 handling in each backend decide their
 	 * own fate.
 	 */
-	CancelDBBackends(InvalidOid, reason);
+	SignalRecoveryConflictWithDatabase(InvalidOid, reason);
 }
 
 /*
@@ -1489,35 +1490,33 @@ LogStandbyInvalidations(int nmsgs, SharedInvalidationMessage *msgs,
 
 /* Return the description of recovery conflict */
 static const char *
-get_recovery_conflict_desc(ProcSignalReason reason)
+get_recovery_conflict_desc(RecoveryConflictReason reason)
 {
 	const char *reasonDesc = _("unknown reason");
 
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case RECOVERY_CONFLICT_BUFFERPIN:
 			reasonDesc = _("recovery conflict on buffer pin");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case RECOVERY_CONFLICT_LOCK:
 			reasonDesc = _("recovery conflict on lock");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case RECOVERY_CONFLICT_TABLESPACE:
 			reasonDesc = _("recovery conflict on tablespace");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case RECOVERY_CONFLICT_SNAPSHOT:
 			reasonDesc = _("recovery conflict on snapshot");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case RECOVERY_CONFLICT_LOGICALSLOT:
 			reasonDesc = _("recovery conflict on replication slot");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			reasonDesc = _("recovery conflict on buffer deadlock");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case RECOVERY_CONFLICT_DATABASE:
 			reasonDesc = _("recovery conflict on database");
 			break;
-		default:
-			break;
 	}
 
 	return reasonDesc;
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 6455bb6e01f..2405b59b501 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -506,6 +506,7 @@ InitProcess(void)
 			Assert(dlist_is_empty(&(MyProc->myProcLocks[i])));
 	}
 #endif
+	pg_atomic_write_u32(&MyProc->pendingRecoveryConflicts, 0);
 
 	/* Initialize fields for sync rep */
 	MyProc->waitLSN = 0;
@@ -1446,7 +1447,7 @@ ProcSleep(LOCALLOCK *locallock)
 					 * because the startup process here has already waited
 					 * longer than deadlock_timeout.
 					 */
-					LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
+					LogRecoveryConflict(RECOVERY_CONFLICT_LOCK,
 										standbyWaitStart, now,
 										cnt > 0 ? vxids : NULL, true);
 					logged_recovery_conflict = true;
@@ -1687,7 +1688,7 @@ ProcSleep(LOCALLOCK *locallock)
 	 * startup process waited longer than deadlock_timeout for it.
 	 */
 	if (InHotStandby && logged_recovery_conflict)
-		LogRecoveryConflict(PROCSIG_RECOVERY_CONFLICT_LOCK,
+		LogRecoveryConflict(RECOVERY_CONFLICT_LOCK,
 							standbyWaitStart, GetCurrentTimestamp(),
 							NULL, false);
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 980ef1da100..a2fa98ee971 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -67,6 +67,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/sinval.h"
+#include "storage/standby.h"
 #include "tcop/backend_startup.h"
 #include "tcop/fastpath.h"
 #include "tcop/pquery.h"
@@ -155,10 +156,6 @@ static const char *userDoption = NULL;	/* -D switch */
 static bool EchoQuery = false;	/* -E switch */
 static bool UseSemiNewlineNewline = false;	/* -j switch */
 
-/* whether or not, and why, we were canceled by conflict with recovery */
-static volatile sig_atomic_t RecoveryConflictPending = false;
-static volatile sig_atomic_t RecoveryConflictPendingReasons[NUM_PROCSIGNALS];
-
 /* reused buffer to pass to SendRowDescriptionMessage() */
 static MemoryContext row_description_context = NULL;
 static StringInfoData row_description_buf;
@@ -2537,34 +2534,31 @@ errdetail_params(ParamListInfo params)
  * Add an errdetail() line showing conflict source.
  */
 static int
-errdetail_recovery_conflict(ProcSignalReason reason)
+errdetail_recovery_conflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case RECOVERY_CONFLICT_BUFFERPIN:
 			errdetail("User was holding shared buffer pin for too long.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case RECOVERY_CONFLICT_LOCK:
 			errdetail("User was holding a relation lock for too long.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case RECOVERY_CONFLICT_TABLESPACE:
 			errdetail("User was or might have been using tablespace that must be dropped.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case RECOVERY_CONFLICT_SNAPSHOT:
 			errdetail("User query might have needed to see row versions that must be removed.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case RECOVERY_CONFLICT_LOGICALSLOT:
 			errdetail("User was using a logical replication slot that must be invalidated.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			errdetail("User transaction caused buffer deadlock with recovery.");
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case RECOVERY_CONFLICT_DATABASE:
 			errdetail("User was connected to a database that must be dropped.");
 			break;
-		default:
-			break;
-			/* no errdetail */
 	}
 
 	return 0;
@@ -3067,15 +3061,14 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to check for a particular type of
+ * Tell the next CHECK_FOR_INTERRUPTS() to check for a particular type of XXX
  * recovery conflict.  Runs in a SIGUSR1 handler.
  */
 void
-HandleRecoveryConflictInterrupt(ProcSignalReason reason)
+HandleRecoveryConflictInterrupt(void)
 {
-	RecoveryConflictPendingReasons[reason] = true;
-	RecoveryConflictPending = true;
-	InterruptPending = true;
+	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
+		InterruptPending = true;
 	/* latch will be set by procsignal_sigusr1_handler */
 }
 
@@ -3083,11 +3076,11 @@ HandleRecoveryConflictInterrupt(ProcSignalReason reason)
  * Check one individual conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
+ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 
 			/*
 			 * If we aren't waiting for a lock we can never deadlock.
@@ -3098,21 +3091,20 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			/* Intentional fall through to check wait for pin */
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case RECOVERY_CONFLICT_BUFFERPIN:
 
 			/*
-			 * If PROCSIG_RECOVERY_CONFLICT_BUFFERPIN is requested but we
-			 * aren't blocking the Startup process there is nothing more to
-			 * do.
+			 * If RECOVERY_CONFLICT_BUFFERPIN is requested but we aren't
+			 * blocking the Startup process there is nothing more to do.
 			 *
-			 * When PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested,
-			 * if we're waiting for locks and the startup process is not
-			 * waiting for buffer pin (i.e., also waiting for locks), we set
-			 * the flag so that ProcSleep() will check for deadlocks.
+			 * When RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested, if we're
+			 * waiting for locks and the startup process is not waiting for
+			 * buffer pin (i.e., also waiting for locks), we set the flag so
+			 * that ProcSleep() will check for deadlocks.
 			 */
 			if (!HoldingBufferPinThatDelaysRecovery())
 			{
-				if (reason == PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
+				if (reason == RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
 					GetStartupBufferPinWaitBufId() < 0)
 					CheckDeadLockAlert();
 				return;
@@ -3121,9 +3113,9 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 			/* Intentional fall through to error handling */
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case RECOVERY_CONFLICT_LOCK:
+		case RECOVERY_CONFLICT_TABLESPACE:
+		case RECOVERY_CONFLICT_SNAPSHOT:
 
 			/*
 			 * If we aren't in a transaction any longer then ignore.
@@ -3133,34 +3125,34 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 
 			/* FALLTHROUGH */
 
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case RECOVERY_CONFLICT_LOGICALSLOT:
 
 			/*
 			 * If we're not in a subtransaction then we are OK to throw an
 			 * ERROR to resolve the conflict.  Otherwise drop through to the
 			 * FATAL case.
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT is a special case that
-			 * always throws an ERROR (ie never promotes to FATAL), though it
-			 * still has to respect QueryCancelHoldoffCount, so it shares this
-			 * code path.  Logical decoding slots are only acquired while
+			 * RECOVERY_CONFLICT_LOGICALSLOT is a special case that always
+			 * throws an ERROR (ie never promotes to FATAL), though it still
+			 * has to respect QueryCancelHoldoffCount, so it shares this code
+			 * path.  Logical decoding slots are only acquired while
 			 * performing logical decoding.  During logical decoding no user
 			 * controlled code is run.  During [sub]transaction abort, the
 			 * slot is released.  Therefore user controlled code cannot
 			 * intercept an error before the replication slot is released.
 			 *
 			 * XXX other times that we can throw just an ERROR *may* be
-			 * PROCSIG_RECOVERY_CONFLICT_LOCK if no locks are held in parent
+			 * RECOVERY_CONFLICT_LOCK if no locks are held in parent
 			 * transactions
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by
-			 * parent transactions and the transaction is not
-			 * transaction-snapshot mode
+			 * RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by parent
+			 * transactions and the transaction is not transaction-snapshot
+			 * mode
 			 *
-			 * PROCSIG_RECOVERY_CONFLICT_TABLESPACE if no temp files or
-			 * cursors open in parent transactions
+			 * RECOVERY_CONFLICT_TABLESPACE if no temp files or cursors open
+			 * in parent transactions
 			 */
-			if (reason == PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT ||
+			if (reason == RECOVERY_CONFLICT_LOGICALSLOT ||
 				!IsSubTransaction())
 			{
 				/*
@@ -3187,8 +3179,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 						 * Re-arm and defer this interrupt until later.  See
 						 * similar code in ProcessInterrupts().
 						 */
-						RecoveryConflictPendingReasons[reason] = true;
-						RecoveryConflictPending = true;
+						(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
 						InterruptPending = true;
 						return;
 					}
@@ -3222,7 +3213,7 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 							 " database and repeat your command.")));
 			break;
 
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case RECOVERY_CONFLICT_DATABASE:
 
 			/* The database is being dropped; terminate the session */
 			pgstat_report_recovery_conflict(reason);
@@ -3243,6 +3234,8 @@ ProcessRecoveryConflictInterrupt(ProcSignalReason reason)
 static void
 ProcessRecoveryConflictInterrupts(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
@@ -3250,17 +3243,21 @@ ProcessRecoveryConflictInterrupts(void)
 	 */
 	Assert(!proc_exit_inprogress);
 	Assert(InterruptHoldoffCount == 0);
-	Assert(RecoveryConflictPending);
 
-	RecoveryConflictPending = false;
+	/* Are any recovery conflict pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
 
-	for (ProcSignalReason reason = PROCSIG_RECOVERY_CONFLICT_FIRST;
-		 reason <= PROCSIG_RECOVERY_CONFLICT_LAST;
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
 		 reason++)
 	{
-		if (RecoveryConflictPendingReasons[reason])
+		if ((pending & (1 << reason)) != 0)
 		{
-			RecoveryConflictPendingReasons[reason] = false;
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
 			ProcessRecoveryConflictInterrupt(reason);
 		}
 	}
@@ -3451,7 +3448,7 @@ ProcessInterrupts(void)
 		}
 	}
 
-	if (RecoveryConflictPending)
+	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
 		ProcessRecoveryConflictInterrupts();
 
 	if (IdleInTransactionSessionTimeoutPending)
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index d7f6d4c5ee6..e6759ccaa3d 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -17,7 +17,7 @@
 
 #include "postgres.h"
 
-#include "storage/procsignal.h"
+#include "storage/standby.h"
 #include "utils/pgstat_internal.h"
 #include "utils/timestamp.h"
 
@@ -88,31 +88,31 @@ pgstat_report_recovery_conflict(int reason)
 
 	dbentry = pgstat_prep_database_pending(MyDatabaseId);
 
-	switch (reason)
+	switch ((RecoveryConflictReason) reason)
 	{
-		case PROCSIG_RECOVERY_CONFLICT_DATABASE:
+		case RECOVERY_CONFLICT_DATABASE:
 
 			/*
 			 * Since we drop the information about the database as soon as it
 			 * replicates, there is no point in counting these conflicts.
 			 */
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_TABLESPACE:
+		case RECOVERY_CONFLICT_TABLESPACE:
 			dbentry->conflict_tablespace++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOCK:
+		case RECOVERY_CONFLICT_LOCK:
 			dbentry->conflict_lock++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_SNAPSHOT:
+		case RECOVERY_CONFLICT_SNAPSHOT:
 			dbentry->conflict_snapshot++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_BUFFERPIN:
+		case RECOVERY_CONFLICT_BUFFERPIN:
 			dbentry->conflict_bufferpin++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT:
+		case RECOVERY_CONFLICT_LOGICALSLOT:
 			dbentry->conflict_logicalslot++;
 			break;
-		case PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			dbentry->conflict_startup_deadlock++;
 			break;
 	}
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index 12b8d4cefaf..c7f7b8bc2dd 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -19,6 +19,7 @@
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/procsignal.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 81f1960a635..090471c1144 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -235,6 +235,15 @@ struct PGPROC
 
 	bool		isRegularBackend;	/* true if it's a regular backend. */
 
+	/*
+	 * While in hot standby mode, shows that a conflict signal has been sent
+	 * for the current transaction. Set/cleared while holding ProcArrayLock,
+	 * though not required. Accessed without lock, if needed.
+	 *
+	 * This is a bitmask of RecoveryConflictReasons
+	 */
+	pg_atomic_uint32 pendingRecoveryConflicts;
+
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index 3a8593f87ba..c5ab1574fe3 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -77,12 +77,15 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   bool excludeXmin0, bool allDbs, int excludeVacuum,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
-extern pid_t SignalVirtualTransaction(VirtualTransactionId vxid, ProcSignalReason sigmode);
+
+extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
+extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
+
 
 extern bool MinimumActiveBackends(int min);
 extern int	CountDBBackends(Oid databaseid);
 extern int	CountDBConnections(Oid databaseid);
-extern void CancelDBBackends(Oid databaseid, ProcSignalReason sigmode);
 extern int	CountUserBackends(Oid roleid);
 extern bool CountOtherDBBackends(Oid databaseId,
 								 int *nbackends, int *nprepared);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index e52b8eb7697..348fba53a93 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -36,20 +36,12 @@ typedef enum
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
 	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
 	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-
-	/* Recovery conflict reasons */
-	PROCSIG_RECOVERY_CONFLICT_FIRST,
-	PROCSIG_RECOVERY_CONFLICT_DATABASE = PROCSIG_RECOVERY_CONFLICT_FIRST,
-	PROCSIG_RECOVERY_CONFLICT_TABLESPACE,
-	PROCSIG_RECOVERY_CONFLICT_LOCK,
-	PROCSIG_RECOVERY_CONFLICT_SNAPSHOT,
-	PROCSIG_RECOVERY_CONFLICT_LOGICALSLOT,
-	PROCSIG_RECOVERY_CONFLICT_BUFFERPIN,
-	PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
-	PROCSIG_RECOVERY_CONFLICT_LAST = PROCSIG_RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
+								 * PGPROC->pendingRecoveryConflicts for the
+								 * reason */
 } ProcSignalReason;
 
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT_LAST + 1)
+#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
 
 typedef enum
 {
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 7b10932635a..27357850ab0 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -16,7 +16,6 @@
 
 #include "datatype/timestamp.h"
 #include "storage/lock.h"
-#include "storage/procsignal.h"
 #include "storage/relfilelocator.h"
 #include "storage/standbydefs.h"
 
@@ -25,6 +24,37 @@ extern PGDLLIMPORT int max_standby_archive_delay;
 extern PGDLLIMPORT int max_standby_streaming_delay;
 extern PGDLLIMPORT bool log_recovery_conflict_waits;
 
+/* Recovery conflict reasons */
+typedef enum
+{
+	/* Backend is connected to a database that is being dropped */
+	RECOVERY_CONFLICT_DATABASE,
+
+	/* Backend is using a tablespace that is being dropped */
+	RECOVERY_CONFLICT_TABLESPACE,
+
+	/* Backend is holding a lock that is blocking recovery */
+	RECOVERY_CONFLICT_LOCK,
+
+	/* Backend is holding a snapshot that is blocking recovery */
+	RECOVERY_CONFLICT_SNAPSHOT,
+
+	/* Backend is using a logical replication slot that must be invalidated */
+	RECOVERY_CONFLICT_LOGICALSLOT,
+
+	/* Backend is holding a pin on a buffer that is blocking recovery */
+	RECOVERY_CONFLICT_BUFFERPIN,
+
+	/*
+	 * The backend is requested to check for deadlocks. The startup process
+	 * doesn't check for deadlock direcly, because we want to kill one of the
+	 * other backends instead of the startup process.
+	 */
+	RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+} RecoveryConflictReason;
+
+#define NUM_RECOVERY_CONFLICT_REASONS (RECOVERY_CONFLICT_STARTUP_DEADLOCK + 1)
+
 extern void InitRecoveryTransactionEnvironment(void);
 extern void ShutdownRecoveryTransactionEnvironment(void);
 
@@ -43,7 +73,7 @@ extern void CheckRecoveryConflictDeadlock(void);
 extern void StandbyDeadLockHandler(void);
 extern void StandbyTimeoutHandler(void);
 extern void StandbyLockTimeoutHandler(void);
-extern void LogRecoveryConflict(ProcSignalReason reason, TimestampTz wait_start,
+extern void LogRecoveryConflict(RecoveryConflictReason reason, TimestampTz wait_start,
 								TimestampTz now, VirtualTransactionId *wait_list,
 								bool still_waiting);
 
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 54ddee875ed..5bc5bcfb20d 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -74,7 +74,7 @@ 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(ProcSignalReason reason);
+extern void HandleRecoveryConflictInterrupt(void);
 extern void ProcessClientReadInterrupt(bool blocked);
 extern void ProcessClientWriteInterrupt(bool blocked);
 
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 34374df0d67..6990f1bd6f7 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2488,6 +2488,7 @@ RecordCacheArrayEntry
 RecordCacheEntry
 RecordCompareData
 RecordIOData
+RecoveryConflictReason
 RecoveryLockEntry
 RecoveryLockXidEntry
 RecoveryPauseState
-- 
2.47.3



  [text/x-patch] v8-0005-Refactor-ProcessRecoveryConflictInterrupt-for-rea.patch (15.6K, ../../[email protected]/6-v8-0005-Refactor-ProcessRecoveryConflictInterrupt-for-rea.patch)
  download | inline diff:
From 507340292d966a51900d3af0bb0f8e454397764e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 22 Jan 2026 22:17:18 +0200
Subject: [PATCH v8 5/8] Refactor ProcessRecoveryConflictInterrupt for
 readability

Two changes here:

1. Introduce a separate RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK flag to
indicate a suspected deadlock that involves a buffer pin. Previously
the startup process used the same flag for a deadlock involving just
regular locks, and to check for deadlocks involving the buffer
pin. The cases are handled separately in the startup process, but the
receiving backend had to deduce which one it was based on
HoldingBufferPinThatDelaysRecovery(). With a separate flag, the
receiver doesn't need to guess.

2. Rewrite the ProcessRecoveryConflictInterrupt() function to not rely
on fallthrough through the switch-statement. That was difficult to
read.
---
 src/backend/storage/ipc/standby.c            |   7 +-
 src/backend/tcop/postgres.c                  | 262 +++++++++++--------
 src/backend/utils/activity/pgstat_database.c |  10 +
 src/include/storage/standby.h                |  10 +-
 4 files changed, 181 insertions(+), 108 deletions(-)

diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 0851789e8b6..d83afbfb9d6 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -859,7 +859,7 @@ ResolveRecoveryConflictWithBufferPin(void)
 		 * not be so harmful because the period that the buffer is kept pinned
 		 * is basically no so long. But we should fix this?
 		 */
-		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+		SendRecoveryConflictWithBufferPin(RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK);
 	}
 
 	/*
@@ -877,7 +877,7 @@ static void
 SendRecoveryConflictWithBufferPin(RecoveryConflictReason reason)
 {
 	Assert(reason == RECOVERY_CONFLICT_BUFFERPIN ||
-		   reason == RECOVERY_CONFLICT_STARTUP_DEADLOCK);
+		   reason == RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK);
 
 	/*
 	 * We send signal to all backends to ask them if they are holding the
@@ -1512,6 +1512,9 @@ get_recovery_conflict_desc(RecoveryConflictReason reason)
 			reasonDesc = _("recovery conflict on replication slot");
 			break;
 		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+			reasonDesc = _("recovery conflict on deadlock");
+			break;
+		case RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK:
 			reasonDesc = _("recovery conflict on buffer deadlock");
 			break;
 		case RECOVERY_CONFLICT_DATABASE:
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a2fa98ee971..bbf2254ca67 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -179,11 +179,15 @@ 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 report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
 static void disable_statement_timeout(void);
 
 
+
 /* ----------------------------------------------------------------
  *		infrastructure for valgrind debugging
  * ----------------------------------------------------------------
@@ -2554,6 +2558,9 @@ errdetail_recovery_conflict(RecoveryConflictReason reason)
 			errdetail("User was using a logical replication slot that must be invalidated.");
 			break;
 		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
+			errdetail("User transaction caused deadlock with recovery.");
+			break;
+		case RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK:
 			errdetail("User transaction caused buffer deadlock with recovery.");
 			break;
 		case RECOVERY_CONFLICT_DATABASE:
@@ -3083,35 +3090,62 @@ ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
 		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 
 			/*
+			 * The startup process is waiting on a lock held by us, and has
+			 * requested us to check if it is a deadlock (i.e. the deadlock
+			 * timeout expired).
+			 *
 			 * If we aren't waiting for a lock we can never deadlock.
 			 */
 			if (GetAwaitedLock() == NULL)
 				return;
 
-			/* Intentional fall through to check wait for pin */
-			/* FALLTHROUGH */
+			/* Set the flag so that ProcSleep() will check for deadlocks. */
+			CheckDeadLockAlert();
+			return;
 
-		case RECOVERY_CONFLICT_BUFFERPIN:
+		case RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK:
 
 			/*
-			 * If RECOVERY_CONFLICT_BUFFERPIN is requested but we aren't
-			 * blocking the Startup process there is nothing more to do.
+			 * The startup process is waiting on a buffer pin, and has
+			 * requested us to check if there is a deadlock involving the pin.
 			 *
-			 * When RECOVERY_CONFLICT_STARTUP_DEADLOCK is requested, if we're
-			 * waiting for locks and the startup process is not waiting for
-			 * buffer pin (i.e., also waiting for locks), we set the flag so
-			 * that ProcSleep() will check for deadlocks.
+			 * If we're not waiting on a lock, there can be no deadlock.
+			 */
+			if (GetAwaitedLock() == NULL)
+				return;
+
+			/*
+			 * If we're not holding the buffer pin, also no deadlock. (The
+			 * startup process doesn't know who's holding the pin, and sends
+			 * this signal to *all* backends, so this is the common case.)
+			 */
+			if (!HoldingBufferPinThatDelaysRecovery())
+				return;
+
+			/*
+			 * Otherwise, we probably have a deadlock.  Unfortunately the
+			 * normal deadlock detector doesn't know about buffer pins, so we
+			 * cannot perform comprehensively deadlock check.  Instead, we
+			 * just assume that it is a deadlock if the above two conditions
+			 * are met.  In principle this can lead to false positives, but
+			 * it's rare in practice because sessions in a hot standby server
+			 * rarely hold locks that can block other backends.
+			 */
+			report_recovery_conflict(reason);
+			return;
+
+		case RECOVERY_CONFLICT_BUFFERPIN:
+
+			/*
+			 * Someone is holding a buffer pin that the startup process is
+			 * waiting for, and it got tired of waiting.  If that's us, error
+			 * out to release the pin.
 			 */
 			if (!HoldingBufferPinThatDelaysRecovery())
-			{
-				if (reason == RECOVERY_CONFLICT_STARTUP_DEADLOCK &&
-					GetStartupBufferPinWaitBufId() < 0)
-					CheckDeadLockAlert();
 				return;
-			}
 
-			/* Intentional fall through to error handling */
-			/* FALLTHROUGH */
+			report_recovery_conflict(reason);
+			return;
 
 		case RECOVERY_CONFLICT_LOCK:
 		case RECOVERY_CONFLICT_TABLESPACE:
@@ -3123,109 +3157,127 @@ ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
 			if (!IsTransactionOrTransactionBlock())
 				return;
 
-			/* FALLTHROUGH */
+			report_recovery_conflict(reason);
+			return;
 
 		case RECOVERY_CONFLICT_LOGICALSLOT:
+			report_recovery_conflict(reason);
+			return;
 
-			/*
-			 * If we're not in a subtransaction then we are OK to throw an
-			 * ERROR to resolve the conflict.  Otherwise drop through to the
-			 * FATAL case.
-			 *
-			 * RECOVERY_CONFLICT_LOGICALSLOT is a special case that always
-			 * throws an ERROR (ie never promotes to FATAL), though it still
-			 * has to respect QueryCancelHoldoffCount, so it shares this code
-			 * path.  Logical decoding slots are only acquired while
-			 * performing logical decoding.  During logical decoding no user
-			 * controlled code is run.  During [sub]transaction abort, the
-			 * slot is released.  Therefore user controlled code cannot
-			 * intercept an error before the replication slot is released.
-			 *
-			 * XXX other times that we can throw just an ERROR *may* be
-			 * RECOVERY_CONFLICT_LOCK if no locks are held in parent
-			 * transactions
-			 *
-			 * RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by parent
-			 * transactions and the transaction is not transaction-snapshot
-			 * mode
-			 *
-			 * RECOVERY_CONFLICT_TABLESPACE if no temp files or cursors open
-			 * in parent transactions
-			 */
-			if (reason == RECOVERY_CONFLICT_LOGICALSLOT ||
-				!IsSubTransaction())
-			{
-				/*
-				 * If we already aborted then we no longer need to cancel.  We
-				 * do this here since we do not wish to ignore aborted
-				 * subtransactions, which must cause FATAL, currently.
-				 */
-				if (IsAbortedTransactionBlockState())
-					return;
+		case RECOVERY_CONFLICT_DATABASE:
+
+			/* The database is being dropped; terminate the session */
+			report_recovery_conflict(reason);
+			break;
+
+		default:
+			elog(FATAL, "unrecognized conflict mode: %d", (int) reason);
+	}
+	elog(FATAL, "unrecognized conflict mode: %d", (int) reason);
+}
+
+/*
+ * This transaction or session is conflicting with recovery and needs to be
+ * killed.  Roll back the transaction, if that's sufficient, or terminate the
+ * connection, or do nothing if we're already in aborted state.
+ */
+static void
+report_recovery_conflict(RecoveryConflictReason reason)
+{
+	bool		fatal;
 
+	if (RECOVERY_CONFLICT_DATABASE)
+	{
+		/* note: no hint about reconnecting, and different errcode */
+		pgstat_report_recovery_conflict(reason);
+		ereport(FATAL,
+				(errcode(ERRCODE_DATABASE_DROPPED),
+				 errmsg("terminating connection due to conflict with recovery"),
+				 errdetail_recovery_conflict(reason)));
+	}
+	if (reason == RECOVERY_CONFLICT_LOGICALSLOT)
+	{
+		/*
+		 * RECOVERY_CONFLICT_LOGICALSLOT is a special case that always throws
+		 * an ERROR (ie never promotes to FATAL), though it still has to
+		 * respect QueryCancelHoldoffCount, so it shares this code path.
+		 * Logical decoding slots are only acquired while performing logical
+		 * decoding.  During logical decoding no user controlled code is run.
+		 * During [sub]transaction abort, the slot is released.  Therefore
+		 * user controlled code cannot intercept an error before the
+		 * replication slot is released.
+		 */
+		fatal = false;
+	}
+	else
+	{
+		fatal = IsSubTransaction();
+	}
+
+	/*
+	 * If we're not in a subtransaction then we are OK to throw an ERROR to
+	 * resolve the conflict.  Otherwise drop through to the FATAL case.
+	 *
+	 * XXX other times that we can throw just an ERROR *may* be
+	 * RECOVERY_CONFLICT_LOCK if no locks are held in parent transactions
+	 *
+	 * RECOVERY_CONFLICT_SNAPSHOT if no snapshots are held by parent
+	 * transactions and the transaction is not transaction-snapshot mode
+	 *
+	 * RECOVERY_CONFLICT_TABLESPACE if no temp files or cursors open in parent
+	 * transactions
+	 */
+	if (!fatal)
+	{
+		/*
+		 * If we already aborted then we no longer need to cancel.  We do this
+		 * here since we do not wish to ignore aborted subtransactions, which
+		 * must cause FATAL, currently.
+		 */
+		if (IsAbortedTransactionBlockState())
+			return;
+
+		/*
+		 * If a recovery conflict happens while we are waiting for input from
+		 * the client, the client is presumably just sitting idle in a
+		 * transaction, preventing recovery from making progress.  We'll drop
+		 * through to the FATAL case below to dislodge it, in that case.
+		 */
+		if (!DoingCommandRead)
+		{
+			/* Avoid losing sync in the FE/BE protocol. */
+			if (QueryCancelHoldoffCount != 0)
+			{
 				/*
-				 * If a recovery conflict happens while we are waiting for
-				 * input from the client, the client is presumably just
-				 * sitting idle in a transaction, preventing recovery from
-				 * making progress.  We'll drop through to the FATAL case
-				 * below to dislodge it, in that case.
+				 * Re-arm and defer this interrupt until later.  See similar
+				 * code in ProcessInterrupts().
 				 */
-				if (!DoingCommandRead)
-				{
-					/* Avoid losing sync in the FE/BE protocol. */
-					if (QueryCancelHoldoffCount != 0)
-					{
-						/*
-						 * Re-arm and defer this interrupt until later.  See
-						 * similar code in ProcessInterrupts().
-						 */
-						(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-						InterruptPending = true;
-						return;
-					}
-
-					/*
-					 * We are cleared to throw an ERROR.  Either it's the
-					 * logical slot case, or we have a top-level transaction
-					 * that we can abort and a conflict that isn't inherently
-					 * non-retryable.
-					 */
-					LockErrorCleanup();
-					pgstat_report_recovery_conflict(reason);
-					ereport(ERROR,
-							(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-							 errmsg("canceling statement due to conflict with recovery"),
-							 errdetail_recovery_conflict(reason)));
-					break;
-				}
+				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
+				InterruptPending = true;
+				return;
 			}
 
 			/*
-			 * The conflict cannot be resolved with ERROR, so terminate the
-			 * whole session.
+			 * We are cleared to throw an ERROR.  Either it's the logical slot
+			 * case, or we have a top-level transaction that we can abort and
+			 * a conflict that isn't inherently non-retryable.
 			 */
+			LockErrorCleanup();
 			pgstat_report_recovery_conflict(reason);
-			ereport(FATAL,
+			ereport(ERROR,
 					(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
-					 errmsg("terminating connection due to conflict with recovery"),
-					 errdetail_recovery_conflict(reason),
-					 errhint("In a moment you should be able to reconnect to the"
-							 " database and repeat your command.")));
-			break;
-
-		case RECOVERY_CONFLICT_DATABASE:
-
-			/* The database is being dropped; terminate the session */
-			pgstat_report_recovery_conflict(reason);
-			ereport(FATAL,
-					(errcode(ERRCODE_DATABASE_DROPPED),
-					 errmsg("terminating connection due to conflict with recovery"),
+					 errmsg("canceling statement due to conflict with recovery"),
 					 errdetail_recovery_conflict(reason)));
-			break;
-
-		default:
-			elog(FATAL, "unrecognized conflict mode: %d", (int) reason);
+		}
 	}
+
+	pgstat_report_recovery_conflict(reason);
+	ereport(FATAL,
+			(errcode(ERRCODE_T_R_SERIALIZATION_FAILURE),
+			 errmsg("terminating connection due to conflict with recovery"),
+			 errdetail_recovery_conflict(reason),
+			 errhint("In a moment you should be able to reconnect to the"
+					 " database and repeat your command.")));
 }
 
 /*
diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c
index e6759ccaa3d..6309909bcd0 100644
--- a/src/backend/utils/activity/pgstat_database.c
+++ b/src/backend/utils/activity/pgstat_database.c
@@ -115,6 +115,16 @@ pgstat_report_recovery_conflict(int reason)
 		case RECOVERY_CONFLICT_STARTUP_DEADLOCK:
 			dbentry->conflict_startup_deadlock++;
 			break;
+		case RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK:
+
+			/*
+			 * The difference between RECOVERY_CONFLICT_STARTUP_DEADLOCK and
+			 * RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK is merely whether a buffer
+			 * pin was part of the deadlock. We use the same counter for both
+			 * reasons.
+			 */
+			dbentry->conflict_startup_deadlock++;
+			break;
 	}
 }
 
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 27357850ab0..c1815e37474 100644
--- a/src/include/storage/standby.h
+++ b/src/include/storage/standby.h
@@ -51,9 +51,17 @@ typedef enum
 	 * other backends instead of the startup process.
 	 */
 	RECOVERY_CONFLICT_STARTUP_DEADLOCK,
+
+	/*
+	 * Like RECOVERY_CONFLICT_STARTUP_DEADLOCK is, but the suspected deadlock
+	 * involves a buffer pin that some other backend is holding. That needs
+	 * special checking because the normal deadlock detector doesn't track the
+	 * buffer pins.
+	 */
+	RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK,
 } RecoveryConflictReason;
 
-#define NUM_RECOVERY_CONFLICT_REASONS (RECOVERY_CONFLICT_STARTUP_DEADLOCK + 1)
+#define NUM_RECOVERY_CONFLICT_REASONS (RECOVERY_CONFLICT_BUFFERPIN_DEADLOCK + 1)
 
 extern void InitRecoveryTransactionEnvironment(void);
 extern void ShutdownRecoveryTransactionEnvironment(void);
-- 
2.47.3



  [text/x-patch] v8-0006-Centralize-resetting-SIGCHLD-handler.patch (9.7K, ../../[email protected]/7-v8-0006-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From b420837a0f8798174327931183fece037630afd9 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Jan 2026 20:22:43 +0200
Subject: [PATCH v8 6/8] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 22379de1e31..c62469a2a81 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -412,7 +412,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1424,7 +1423,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 65deabe91a7..ed74d966026 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -790,7 +790,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 80e3088fc7e..a3594875c34 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -109,11 +109,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 6482c21b8f9..1b88ef920b4 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -221,11 +221,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 1a20387c4bd..94f4f828a86 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -238,9 +238,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index a1a4f65f9a9..cb1a767858b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -235,11 +235,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 1c443b3d126..9293849d772 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -284,11 +284,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index c3d56c866d3..1e7a98b02c3 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -267,11 +267,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 38ec8a4c8c7..aae5d3c861c 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -112,11 +112,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 60fc756b509..5ffe177f940 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1599,7 +1599,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 6970af3f3ff..5b3d7fcf3d7 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -258,9 +258,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index a0e6a3d200c..3f9a427f7c6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3732,9 +3732,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index bbf2254ca67..06ae1e3edbc 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4279,13 +4279,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
-									 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..608f7ad7abb 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -142,6 +142,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -154,6 +163,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] v8-0007-Use-standard-die-handler-for-SIGTERM-in-bgworkers.patch (2.0K, ../../[email protected]/8-v8-0007-Use-standard-die-handler-for-SIGTERM-in-bgworkers.patch)
  download | inline diff:
From 1b0fa24f09d69e41881c33ed6bdec8f023f91f43 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 20 Jan 2026 16:57:25 +0200
Subject: [PATCH v8 7/8] Use standard die() handler for SIGTERM in bgworkers

---
 doc/src/sgml/bgworker.sgml        |  2 ++
 src/backend/postmaster/bgworker.c | 16 +---------------
 2 files changed, 3 insertions(+), 15 deletions(-)

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 4699ef6345f..b239e38aaae 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -254,6 +254,8 @@ typedef struct BackgroundWorker
    <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.
+
+   TODO document that: you should call CHECK_FOR_INTERRUPTS(), to react promptly to terminate request (SIGTERM)
   </para>
 
   <para>
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index ed74d966026..06cfadaf88a 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -712,20 +712,6 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
 	return true;
 }
 
-/*
- * Standard SIGTERM handler for background workers
- */
-static void
-bgworker_die(SIGNAL_ARGS)
-{
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
-
-	ereport(FATAL,
-			(errcode(ERRCODE_ADMIN_SHUTDOWN),
-			 errmsg("terminating background worker \"%s\" due to administrator command",
-					MyBgworkerEntry->bgw_type)));
-}
-
 /*
  * Main entry point for background worker processes.
  */
@@ -782,7 +768,7 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, bgworker_die);
+	pqsignal(SIGTERM, die);
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGHUP, SIG_IGN);
 
-- 
2.47.3



  [text/x-patch] v8-0008-Replace-Latches-with-Interrupts.patch (508.3K, ../../[email protected]/9-v8-0008-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 43ccb4ecda883c1b538922a2212ac3666d63d6a0 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 29 Jan 2026 02:07:45 +0200
Subject: [PATCH v8 8/8] Replace Latches with Interrupts

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

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

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

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

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

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

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() clears the interrupts bits that it
processes, the caller is no longer required to do ClearInterrupt (or
ResetLatch) for those bits. If you wake up for other interrupts that
are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear
those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_WAIT_WAKEUP interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

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

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

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

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/[email protected]
  Discussion: https://www.postgresql.org/message-id/[email protected]

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

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

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

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

XXX: original text from Thomas's patch
--------------------------------------

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

SIGUSR1 can now be set to SIG_IGN in most backends.  Some special cases
remain, that were not using the general sigusr1_handler (now removed).
---
 contrib/pg_prewarm/autoprewarm.c              |  63 +-
 contrib/postgres_fdw/connection.c             |  21 +-
 contrib/postgres_fdw/postgres_fdw.c           |   4 +-
 doc/src/sgml/bgworker.sgml                    |  32 +-
 doc/src/sgml/oauth-validators.sgml            |   2 +-
 doc/src/sgml/sources.sgml                     |  11 +-
 src/backend/Makefile                          |   1 +
 src/backend/access/brin/brin_pageops.c        |   2 +-
 src/backend/access/brin/brin_revmap.c         |   2 +-
 src/backend/access/common/toast_internals.c   |   2 +-
 src/backend/access/gin/ginbtree.c             |   2 +-
 src/backend/access/gin/gindatapage.c          |   2 +-
 src/backend/access/gin/ginfast.c              |   1 +
 src/backend/access/gin/ginget.c               |   1 +
 src/backend/access/gin/ginutil.c              |   2 +-
 src/backend/access/gin/ginvacuum.c            |   1 +
 src/backend/access/gist/gist.c                |   1 +
 src/backend/access/gist/gistget.c             |   2 +-
 src/backend/access/gist/gistvacuum.c          |   2 +-
 src/backend/access/hash/hash.c                |   2 +-
 src/backend/access/hash/hashinsert.c          |   2 +-
 src/backend/access/hash/hashovfl.c            |   2 +-
 src/backend/access/hash/hashpage.c            |   2 +-
 src/backend/access/hash/hashsearch.c          |   2 +-
 src/backend/access/hash/hashsort.c            |   2 +-
 src/backend/access/heap/pruneheap.c           |   2 +-
 src/backend/access/heap/vacuumlazy.c          |  10 +-
 src/backend/access/heap/visibilitymap.c       |   2 +-
 src/backend/access/index/genam.c              |   1 +
 src/backend/access/nbtree/nbtdedup.c          |   2 +-
 src/backend/access/nbtree/nbtinsert.c         |   2 +-
 src/backend/access/nbtree/nbtpage.c           |   1 +
 src/backend/access/nbtree/nbtsearch.c         |   2 +-
 src/backend/access/spgist/spgdoinsert.c       |   9 +-
 src/backend/access/spgist/spginsert.c         |   2 +-
 src/backend/access/spgist/spgscan.c           |   2 +-
 src/backend/access/spgist/spgvacuum.c         |   2 +-
 src/backend/access/transam/README             |   2 +-
 src/backend/access/transam/README.parallel    |   2 +-
 src/backend/access/transam/generic_xlog.c     |   2 +-
 src/backend/access/transam/parallel.c         |  78 +--
 src/backend/access/transam/slru.c             |   2 +-
 src/backend/access/transam/twophase.c         |   1 +
 src/backend/access/transam/xlog.c             |  14 +-
 src/backend/access/transam/xlogfuncs.c        |  12 +-
 src/backend/access/transam/xlogrecovery.c     | 120 ++--
 src/backend/access/transam/xlogwait.c         |  39 +-
 src/backend/backup/basebackup_throttle.c      |  18 +-
 src/backend/bootstrap/bootparse.y             |   2 +-
 src/backend/commands/async.c                  | 151 ++---
 src/backend/commands/copyfromparse.c          |  11 +-
 src/backend/commands/vacuum.c                 |  13 +-
 src/backend/executor/nodeAppend.c             |  24 +-
 src/backend/executor/nodeGather.c             |  10 +-
 src/backend/ipc/Makefile                      |  19 +
 src/backend/ipc/README.md                     | 272 ++++++++
 src/backend/ipc/interrupt.c                   | 418 ++++++++++++
 src/backend/ipc/meson.build                   |   6 +
 src/backend/ipc/signal_handlers.c             | 155 +++++
 src/backend/libpq/auth.c                      |   9 +-
 src/backend/libpq/be-secure-gssapi.c          |  15 +-
 src/backend/libpq/be-secure-openssl.c         |   7 +-
 src/backend/libpq/be-secure.c                 |  61 +-
 src/backend/libpq/pqcomm.c                    |  36 +-
 src/backend/libpq/pqmq.c                      |  29 +-
 src/backend/meson.build                       |   1 +
 src/backend/postmaster/Makefile               |   1 -
 src/backend/postmaster/autovacuum.c           | 157 ++---
 src/backend/postmaster/auxprocess.c           |   2 +-
 src/backend/postmaster/bgworker.c             | 168 ++---
 src/backend/postmaster/bgwriter.c             |  58 +-
 src/backend/postmaster/checkpointer.c         | 194 +++---
 src/backend/postmaster/interrupt.c            | 108 ----
 src/backend/postmaster/meson.build            |   1 -
 src/backend/postmaster/pgarch.c               | 103 ++-
 src/backend/postmaster/postmaster.c           |  90 ++-
 src/backend/postmaster/startup.c              | 113 ++--
 src/backend/postmaster/syslogger.c            |  37 +-
 src/backend/postmaster/walsummarizer.c        |  84 ++-
 src/backend/postmaster/walwriter.c            |  46 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   1 -
 .../replication/logical/applyparallelworker.c | 141 ++--
 src/backend/replication/logical/launcher.c    | 126 ++--
 .../replication/logical/sequencesync.c        |   6 +-
 src/backend/replication/logical/slotsync.c    |  90 +--
 src/backend/replication/logical/tablesync.c   |  37 +-
 src/backend/replication/logical/worker.c      |  49 +-
 src/backend/replication/slot.c                |  37 +-
 src/backend/replication/syncrep.c             |  42 +-
 src/backend/replication/walreceiver.c         |  60 +-
 src/backend/replication/walreceiverfuncs.c    |   3 +-
 src/backend/replication/walsender.c           | 212 +++---
 src/backend/storage/aio/aio.c                 |   2 +-
 src/backend/storage/aio/aio_io.c              |   2 +-
 src/backend/storage/aio/method_worker.c       |  73 ++-
 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            |   2 +
 src/backend/storage/ipc/ipc.c                 |   9 +-
 src/backend/storage/ipc/latch.c               | 389 -----------
 src/backend/storage/ipc/meson.build           |   1 -
 src/backend/storage/ipc/pmsignal.c            | 100 ++-
 src/backend/storage/ipc/procarray.c           |  22 +-
 src/backend/storage/ipc/procsignal.c          | 225 +------
 src/backend/storage/ipc/shm_mq.c              | 127 ++--
 src/backend/storage/ipc/signalfuncs.c         |  12 +-
 src/backend/storage/ipc/sinval.c              |  60 +-
 src/backend/storage/ipc/sinvaladt.c           |  22 +-
 src/backend/storage/ipc/standby.c             |  37 +-
 src/backend/storage/ipc/waiteventset.c        | 396 ++++++------
 src/backend/storage/lmgr/condition_variable.c |  35 +-
 src/backend/storage/lmgr/predicate.c          |   9 +-
 src/backend/storage/lmgr/proc.c               | 136 ++--
 src/backend/storage/smgr/smgr.c               |   4 +-
 src/backend/storage/sync/sync.c               |   6 +-
 src/backend/tcop/backend_startup.c            |   8 +
 src/backend/tcop/postgres.c                   | 603 +++++++++---------
 src/backend/utils/adt/jsonpath_gram.y         |   2 +-
 src/backend/utils/adt/mcxtfuncs.c             |  11 +-
 src/backend/utils/adt/misc.c                  |  25 +-
 src/backend/utils/adt/timestamp.c             |   4 +-
 src/backend/utils/error/elog.c                |   1 -
 src/backend/utils/init/globals.c              |  23 -
 src/backend/utils/init/miscinit.c             |  78 +--
 src/backend/utils/init/postinit.c             |  42 +-
 src/backend/utils/misc/timeout.c              |   7 -
 src/backend/utils/mmgr/mcxt.c                 |  24 +-
 src/common/scram-common.c                     |   2 +-
 src/include/Makefile                          |   1 +
 src/include/access/parallel.h                 |   2 -
 src/include/commands/async.h                  |   6 -
 src/include/ipc/interrupt.h                   | 364 +++++++++++
 .../interrupt.h => ipc/signal_handlers.h}     |   6 +-
 src/include/libpq/libpq-be-fe-helpers.h       |  48 +-
 src/include/libpq/libpq.h                     |   4 +-
 src/include/libpq/pqmq.h                      |   2 +-
 src/include/meson.build                       |   1 +
 src/include/miscadmin.h                       | 135 +---
 src/include/postmaster/bgworker.h             |  10 +-
 src/include/postmaster/bgworker_internals.h   |   4 +-
 src/include/postmaster/startup.h              |   4 +-
 src/include/replication/logicalworker.h       |   5 -
 src/include/replication/slot.h                |   2 +-
 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                   | 140 ----
 src/include/storage/pmsignal.h                |   5 +
 src/include/storage/proc.h                    |  35 +-
 src/include/storage/procarray.h               |   2 +-
 src/include/storage/procsignal.h              |  33 -
 src/include/storage/sinval.h                  |   5 -
 src/include/storage/waiteventset.h            |  35 +-
 src/include/tcop/tcopprot.h                   |   7 -
 src/include/utils/memutils.h                  |   1 -
 src/include/utils/pgstat_internal.h           |   1 +
 src/port/pg_numa.c                            |   7 +-
 src/port/pgsleep.c                            |   8 +-
 src/test/isolation/isolationtester.c          |   2 +-
 src/test/modules/test_shm_mq/setup.c          |  15 +-
 src/test/modules/test_shm_mq/test.c           |  16 +-
 src/test/modules/test_shm_mq/test_shm_mq.h    |   2 +
 src/test/modules/test_shm_mq/worker.c         |  19 +-
 src/test/modules/worker_spi/worker_spi.c      |  45 +-
 src/tools/pgindent/typedefs.list              |   4 +-
 166 files changed, 3612 insertions(+), 3618 deletions(-)
 create mode 100644 src/backend/ipc/Makefile
 create mode 100644 src/backend/ipc/README.md
 create mode 100644 src/backend/ipc/interrupt.c
 create mode 100644 src/backend/ipc/meson.build
 create mode 100644 src/backend/ipc/signal_handlers.c
 delete mode 100644 src/backend/postmaster/interrupt.c
 delete mode 100644 src/backend/storage/ipc/latch.c
 create mode 100644 src/include/ipc/interrupt.h
 rename src/include/{postmaster/interrupt.h => ipc/signal_handlers.h} (82%)
 delete mode 100644 src/include/storage/latch.h

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 89e187425cc..6be5ca9c183 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -170,10 +168,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. */
@@ -222,27 +223,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
 		{
@@ -266,14 +267,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);
 	}
 
 	/*
@@ -423,7 +423,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;
 
 		/*
@@ -444,7 +444,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)));
@@ -504,8 +504,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. */
@@ -923,9 +922,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),
@@ -958,9 +954,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 487a1a23170..3bf73e4d2a9 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -829,8 +829,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);
@@ -1483,7 +1483,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))
@@ -1578,7 +1578,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))
@@ -1686,12 +1686,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 3572689e33b..7c819ad48a8 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index b239e38aaae..67a10053bbc 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
@@ -250,7 +261,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.
@@ -283,10 +294,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/backend/Makefile b/src/backend/Makefile
index baa9b05d021..03ec6428690 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 6836786fd05..e053d41a3f4 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 436e54f2066..426259935e7 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 7a6b177977b..55b106d340d 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index d205093e21d..da2c946c7d1 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 11a6674a10b..2a667d65839 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index d5944205db2..d24cf2b75b3 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 11b214eb99b..9e236799f2e 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 632c2427952..9cb4b766795 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 4be267ff657..ae7e7993c8b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,6 +143,7 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
@@ -3300,11 +3301,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 3047bd46def..da1952e9412 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index a29be6f467b..7b97a2fb79d 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 01a89104ef0..400f55e0b06 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -114,9 +115,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -126,9 +124,6 @@ static FixedParallelState *MyFixedParallelState;
 /* List of active parallel contexts. */
 static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list);
 
-/* Backend-local copy of data from FixedParallelState. */
-static pid_t ParallelLeaderPid;
-
 /*
  * List of internal parallel worker entry points.  We need this for
  * reasons explained in LookupParallelWorkerFunction(), below.
@@ -242,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -611,7 +606,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.
@@ -766,16 +760,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();
+				}
 			}
 		}
 
@@ -884,15 +883,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1033,21 +1033,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1082,9 +1067,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1326,8 +1309,10 @@ ParallelWorkerMain(Datum main_arg)
 	/* Set flag to indicate that we're initializing a parallel worker. */
 	InitializingParallelWorker = true;
 
-	/* Establish signal handlers. */
-	pqsignal(SIGTERM, die);
+	/*
+	 * Standard interrupt handlers have already been installed; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Determine and set our parallel worker number. */
@@ -1363,8 +1348,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));
 
@@ -1380,8 +1364,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
@@ -1620,9 +1603,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..5592df30f8d 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 601ce3faa64..f43588b3cf7 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 16614e152dd..b17497e8b1b 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2657,7 +2657,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 		ProcNumber	walwriterProc = procglobal->walwriterProc;
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, ProcGlobal->walwriterProc);
 	}
 }
 
@@ -9510,11 +9510,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2efe4105efb..8c74eceec18 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -24,11 +24,11 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.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"
@@ -718,17 +718,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 117d8d8bb6b..dc24ef98914 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -322,23 +323,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.
 	 */
@@ -473,7 +457,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -547,13 +530,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).
@@ -1654,13 +1630,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);
 }
 
 /*
@@ -1790,8 +1759,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
@@ -1820,8 +1789,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))
 			{
@@ -1849,9 +1818,8 @@ PerformWalRecovery(void)
 			}
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2977,7 +2945,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;
 
@@ -3054,8 +3022,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)
@@ -3063,11 +3031,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;
 
@@ -3088,10 +3061,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;
 }
@@ -3769,16 +3744,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;
@@ -4044,11 +4019,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;
 				}
 
@@ -4064,11 +4040,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 */
@@ -4500,11 +4473,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4542,7 +4515,10 @@ CheckPromoteSignal(void)
 void
 WakeupRecovery(void)
 {
-	SetLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
+	ProcNumber	procno = ((volatile PROC_HDR *) ProcGlobal)->startupProc;
+
+	if (procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAL_ARRIVED, procno);
 }
 
 /*
@@ -4746,7 +4722,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 d286ff63123..efe08c4ebfe 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -68,7 +68,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -241,9 +241,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
@@ -298,14 +298,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);
 }
@@ -364,7 +363,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.
@@ -376,7 +375,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);
@@ -434,8 +433,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
@@ -447,8 +447,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index e112eed7485..e49fdc1efa2 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 657c591618d..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  sets the process's latch, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  main loop in PostgresBackend()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,6 +166,7 @@
 #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"
@@ -177,7 +174,6 @@
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -288,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -318,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -527,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. ProcessNotifyInterrupt() will then be called whenever it's safe to
- * actually deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -552,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1265,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1360,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1386,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1618,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1638,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2242,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2262,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2293,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2304,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2330,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2340,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2360,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2371,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, signalProcnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 }
 
@@ -2530,39 +2496,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted
- *		via the process's latch, and this routine will get called.
- *		If we are truly idle (ie, *not* inside a transaction block),
- *		process the incoming notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if the interrupt occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -2571,12 +2510,13 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
-	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2686,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3050,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 5868a7fa11f..8002ca9805c 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -267,9 +267,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)
@@ -301,7 +305,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03932f45c8a..cc34f40f2ac 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2430,8 +2430,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2440,9 +2439,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();
 	}
@@ -2529,8 +2528,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 7138dc692c6..166b6f5eec6 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
+#include "utils/resowner.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1043,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;
@@ -1067,8 +1067,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,
@@ -1078,8 +1078,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)
@@ -1122,14 +1122,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 4105f1d1968..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,7 +34,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "utils/wait_event.h"
 
@@ -382,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..9c34f2b2501
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,272 @@
+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.
+
+
+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 thse 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. Hence, it still uses the above Unix signals on the child
+processes.
+
+There are some exceptions to these:
+
+- 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.
+
+- The postmaster uses SIGUSR1 to request the logger process to rotate
+  the log. Interrupts cannot be sent to the logger process because it
+  does not have a PGPROC entry and refrains from accessing shared
+  memory in general.
+
+
+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. If we moved the
+interrupt mask to PMChildSlot, it would be easier for postmaster.
+However, PGPROC seems like a more natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+Currently, there's a PGPROC pointer in PMChildSlot, which the
+postmaster uses to find the PGPROC entry. But that only works after
+the child process has acquired a PGPROC entry. So there's a small
+window after forking a process where the postmaster cannot send
+interrupts to it.
+
+
+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: Extensions
+---------------
+
+Extensions should be able to allocate interrupts bits for their own
+purposes. This is not implemented.
+
+TODO: More interrupt bits
+-------------------------
+
+There are currently 7 bits out of 32 available. That doesn't leave a
+lot of headroom for the future, especially when extensions can also
+allocate some bits.
+
+Options:
+
+a) Go from 32 to 64 atomics. Simple, assuming there's no performance
+   difference. But the bits will still be a somewhat scarce resource.
+
+b) Use multiple words for the pending interrupts, and another flag to
+   indicate that "something is pending". That might add some instructions
+   to the hot paths of CHECK_FOR_INTERRUPTS().
+
+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..17962027f22
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,418 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[32];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint32 LocalPendingInterrupts;
+
+pg_atomic_uint32 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+/*
+ * 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 bits, but this isn't
+	 * performance critical.
+	 */
+	for (uint32 i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & (1 << 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
+	for (uint32 i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & (1 << i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
+ *
+ * If an interrupt condition is pending, and it's safe to service it,
+ * then clear the flag and call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning.  (This is not the same as guaranteeing that it's still clear
+ * when we return; another interrupt could have arrived.  But we promise that
+ * any pre-existing one will have been serviced.)
+ */
+void
+ProcessInterrupts(void)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/*
+	 * TODO: it'd be good for performance to read the atomic
+	 * MyPendingInterrupts variable just once in this function
+	 */
+	interruptsToProcess =
+		pg_atomic_read_u32(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (uint32 i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		uint32		interrupt = (1 << i);
+
+		if ((interruptsToProcess & interrupt) != 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(interrupt);
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u32(MyPendingInterrupts,
+						   pg_atomic_exchange_u32(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint32		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u32(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint32		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u32(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt %08x to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in aux processes that want
+ * to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..f9473d748ed
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,155 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is overridden in some processes. FIXME: but they probably
+	 * should use interrupts now
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/* FIXME: why bother? This is also SIG_IGN in postmaster */
+	pqsignal(SIGALRM, SIG_IGN);
+	/* FIXME:established in InitializeTimeouts */
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * 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.
+ * (FIXME: check if that's still accurate)
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 795bfed8d19..314d34a9591 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index df7dc79b827..1a60ca8f78a 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,6 +15,7 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
@@ -421,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.
  *
@@ -457,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
@@ -496,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
@@ -678,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 4da6ac22ff9..3a0ff956823 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -533,8 +534,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 3f9257ab010..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,8 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -174,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -181,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -213,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -255,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -284,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -300,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -307,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -338,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -349,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -364,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 6570f27297b..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,8 +73,8 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
@@ -175,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);
@@ -287,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
@@ -306,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;
 }
@@ -1411,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;
 		}
 
@@ -2062,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 6e4bbfb5aa1..0a029eb02a8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -25,7 +26,6 @@
 
 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);
@@ -75,14 +75,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;
 }
 
@@ -169,27 +168,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
+			SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+						  pq_mq_parallel_leader_proc_number);
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 712a857cdb4..d20e6fba285 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c62469a2a81..07a9920bc08 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, and sends INTERRUPT_VACUUM_WORKER_FINISHED to the launcher.
+ * 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 it sends INTERRUPT_VACUUM_WORKER_FINISHED
+ * to the launcher.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -151,9 +153,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;
@@ -277,7 +276,7 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
+ * av_launcherProc	the proc number of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -288,12 +287,15 @@ 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
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
+	ProcNumber	av_launcherProc;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -324,7 +326,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);
@@ -358,7 +360,6 @@ static void perform_work_item(AutoVacuumWorkItem *workitem);
 static void autovac_report_activity(autovac_table *tab);
 static void autovac_report_workitem(AutoVacuumWorkItem *workitem,
 									const char *nspname, const char *relname);
-static void avl_sigusr2_handler(SIGNAL_ARGS);
 static bool av_worker_available(void);
 static void check_av_worker_gucs(void);
 
@@ -397,21 +398,19 @@ 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
+	 * Set up 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 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	/* 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
@@ -458,7 +457,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();
@@ -500,7 +500,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();
 
 		/*
@@ -559,12 +559,12 @@ 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 */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
+	AutoVacuumShmem->av_launcherProc = MyProcNumber;
 
 	/*
 	 * Create the initial database list.  The invariant we want this list to
@@ -576,41 +576,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -746,21 +749,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? */
@@ -778,17 +777,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();
 }
 
 /*
@@ -799,7 +787,7 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
+	AutoVacuumShmem->av_launcherProc = INVALID_PROC_NUMBER;
 
 	proc_exit(0);				/* done */
 }
@@ -1357,8 +1345,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)
@@ -1366,14 +1354,6 @@ AutoVacWorkerFailed(void)
 	AutoVacuumShmem->av_signal[AutoVacForkFailed] = true;
 }
 
-/* SIGUSR2: a worker is up and running, or just finished, or failed to fork */
-static void
-avl_sigusr2_handler(SIGNAL_ARGS)
-{
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
-}
-
 
 /********************************************************************
  *					  AUTOVACUUM WORKER CODE
@@ -1407,22 +1387,17 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
+	SetStandardInterruptHandlers();
 
 	/*
 	 * SIGINT is used to signal canceling the current table's vacuum; SIGTERM
-	 * means abort and exit cleanly, and SIGQUIT means abandon ship.
+	 * means abort and exit cleanly, and SIGQUIT means abandon ship. XXX
 	 */
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1549,8 +1524,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		if (AutoVacuumShmem->av_launcherProc != INVALID_PROC_NUMBER)
+			SendInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED, AutoVacuumShmem->av_launcherProc);
 	}
 	else
 	{
@@ -2292,9 +2267,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);
 
 			/*
@@ -2452,7 +2426,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();
 		{
@@ -2541,9 +2515,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();
 		}
@@ -2665,7 +2638,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();
 	{
@@ -3393,7 +3366,7 @@ AutoVacuumShmemInit(void)
 
 		Assert(!found);
 
-		AutoVacuumShmem->av_launcherpid = 0;
+		AutoVacuumShmem->av_launcherProc = INVALID_PROC_NUMBER;
 		dclist_init(&AutoVacuumShmem->av_freeWorkers);
 		dlist_init(&AutoVacuumShmem->av_runningWorkers);
 		AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index 8ea800c0bbd..ee16e9cb025 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 06cfadaf88a..127aec934c9 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -79,6 +79,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;
 
 /*
@@ -196,8 +198,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -238,6 +241,14 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -319,20 +330,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;
@@ -340,8 +351,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;
 		}
@@ -387,23 +398,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;
@@ -425,7 +419,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.
  */
@@ -470,8 +464,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);
 }
 
 /*
@@ -487,12 +481,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
@@ -505,27 +499,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;
+		}
 	}
 }
 
@@ -557,14 +558,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);
 		}
 	}
 }
@@ -618,11 +619,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;
 		}
 	}
 }
@@ -751,32 +747,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, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -982,15 +974,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
@@ -1106,6 +1089,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.
@@ -1206,8 +1208,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)
@@ -1227,9 +1230,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)
 		{
@@ -1237,7 +1240,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1251,8 +1254,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)
@@ -1270,9 +1274,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)
 		{
@@ -1280,7 +1284,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 a3594875c34..14ba3aeb82d 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -98,16 +99,20 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * No query cancellation possible, so no need for the signal handler
+	 * either.
+	 *
+	 * FIXME: should we just keep the signal handler anyway?  It's harmless,
+	 * as we will ignore the interrupt that it raises
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	/*
+	 * Set up interrupt handling
+	 */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -221,9 +226,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 +301,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 +331,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 1b88ef920b4..c2fa1009b70 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -44,12 +44,13 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -84,10 +85,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
@@ -162,7 +164,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;
@@ -174,16 +175,13 @@ 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);
 static bool CompactCheckpointerRequestQueue(void);
 static void UpdateSharedMemoryConfig(void);
 
-/* Signal handlers */
-static void ReqShutdownXLOG(SIGNAL_ARGS);
-
 
 /*
  * Main entry point for checkpointer process
@@ -205,21 +203,20 @@ 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(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/* 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.
@@ -365,15 +362,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;
 
 		/*
@@ -547,10 +544,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;
 		}
 
@@ -575,7 +572,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);
@@ -591,10 +588,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);
 	}
 
 	/*
@@ -603,7 +603,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -621,7 +621,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);
 	}
 
 	/*
@@ -631,55 +631,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();
 }
 
 /*
@@ -791,24 +774,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;
 
@@ -823,10 +800,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)
 	{
@@ -838,10 +817,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -925,20 +900,6 @@ IsCheckpointOnSchedule(double progress)
 }
 
 
-/* --------------------------------
- *		signal handler routines
- * --------------------------------
- */
-
-/* SIGINT: set flag to trigger writing of shutdown checkpoint */
-static void
-ReqShutdownXLOG(SIGNAL_ARGS)
-{
-	ShutdownXLOGPending = true;
-	SetLatch(MyLatch);
-}
-
-
 /* --------------------------------
  *		communication with backends
  * --------------------------------
@@ -1108,14 +1069,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++)
@@ -1134,7 +1096,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1266,7 +1228,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 	}
 
 	return true;
@@ -1547,5 +1509,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * Simple signal handler for triggering a configuration reload.
- *
- * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending should be checked at a convenient place within the
- * main loop, or else the main loop should call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForShutdownRequest(SIGNAL_ARGS)
-{
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 94f4f828a86..134402c41a2 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -132,11 +132,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
  * ----------
@@ -148,10 +143,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -227,17 +223,31 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 
 	/*
 	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT. XXX
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+
+	/*
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install
+	 */
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -248,8 +258,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 +293,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 +307,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 +318,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 +327,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -335,7 +345,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 +357,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -357,10 +368,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -409,7 +423,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 +431,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 +864,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 d6133bfebc6..b73006fcca9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -442,6 +442,7 @@ static CAC_state canAcceptConnections(BackendType backend_type);
 static void signal_child(PMChild *pmchild, int signal);
 static bool SignalChildren(int signal, BackendTypeMask targetMask);
 static void TerminateChildren(int signal);
+static void SendInterruptToPMChild(PMChild *child, uint32 interruptMask);
 static int	CountChildren(BackendTypeMask targetMask);
 static void LaunchMissingBackgroundProcesses(void);
 static void maybe_start_bgworkers(void);
@@ -539,10 +540,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +558,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +578,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1645,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1682,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)
@@ -1732,7 +1734,7 @@ ServerLoop(void)
 		{
 			avlauncher_needs_signal = false;
 			if (AutoVacLauncherPMChild != NULL)
-				signal_child(AutoVacLauncherPMChild, SIGUSR2);
+				SendInterruptToPMChild(AutoVacLauncherPMChild, INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 		}
 
 #ifdef HAVE_PTHREAD_IS_THREADED_NP
@@ -1985,7 +1987,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1997,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2074,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2235,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2259,6 +2261,7 @@ process_pm_child_exit(void)
 		 */
 		if (StartupPMChild && pid == StartupPMChild->pid)
 		{
+			elog(LOG, "STARTUP PROC EXITED");
 			ReleasePostmasterChildSlot(StartupPMChild);
 			StartupPMChild = NULL;
 
@@ -2581,6 +2584,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2632,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2660,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
@@ -2670,7 +2675,7 @@ CleanupBackend(PMChild *bp,
 	 * the remaining workers.
 	 */
 	if (bp_bkend_type == B_AUTOVAC_WORKER && AutoVacLauncherPMChild != NULL)
-		signal_child(AutoVacLauncherPMChild, SIGUSR2);
+		SendInterruptToPMChild(AutoVacLauncherPMChild, INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 
 	/*
 	 * If it was a background worker, also update its RegisteredBgWorker
@@ -3054,7 +3059,7 @@ PostmasterStateMachine(void)
 				/* And tell it to write the shutdown checkpoint */
 				if (CheckpointerPMChild != NULL)
 				{
-					signal_child(CheckpointerPMChild, SIGINT);
+					SendInterruptToPMChild(CheckpointerPMChild, INTERRUPT_SHUTDOWN_XLOG);
 					UpdatePMState(PM_WAIT_XLOG_SHUTDOWN);
 				}
 				else
@@ -3121,7 +3126,7 @@ PostmasterStateMachine(void)
 			 * interfering.
 			 */
 			if (CheckpointerPMChild != NULL)
-				signal_child(CheckpointerPMChild, SIGUSR2);
+				SendInterruptToPMChild(CheckpointerPMChild, INTERRUPT_TERMINATE);
 		}
 	}
 
@@ -3543,6 +3548,19 @@ TerminateChildren(int signal)
 	}
 }
 
+static void
+SendInterruptToPMChild(PMChild *child, uint32 interruptMask)
+{
+	ProcNumber	procno = GetPMChildProcNumber(child->child_slot);
+
+	/*
+	 * FIXME: If the child hasn't done InitProcess yet, it has no PGPROC entry
+	 * and the interrupt will be lost.
+	 */
+	if (procno != INVALID_PROC_NUMBER)
+		SendInterrupt(interruptMask, procno);
+}
+
 /*
  * BackendStartup -- start backend process
  *
@@ -3845,6 +3863,8 @@ process_pm_pmsignal(void)
 			/*
 			 * Waken walsenders for the last time. No regular backends should
 			 * be around anymore.
+			 *
+			 * FIXME: send INTERRUPT_WALSND_STOP instead
 			 */
 			SignalChildren(SIGUSR2, btmask(B_WAL_SENDER));
 
@@ -3907,10 +3927,16 @@ process_pm_pmsignal(void)
 		/*
 		 * Tell startup process to finish recovery.
 		 *
+		 * 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.
+		 *
 		 * Leave the promote signal file in place and let the Startup process
 		 * do the unlink.
 		 */
-		signal_child(StartupPMChild, SIGUSR2);
+		SendInterruptToPMChild(StartupPMChild,
+							   INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED
+			);
 	}
 }
 
@@ -3921,7 +3947,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4297,15 +4323,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 cb1a767858b..fa1d4d6a8e8 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * Flags set by interrupt handlers for later service in the redo loop.
- */
-static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t shutdown_requested = false;
-static volatile sig_atomic_t promote_signaled = false;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -75,10 +71,6 @@ static volatile sig_atomic_t startup_progress_timer_expired = false;
  */
 int			log_startup_progress_interval = 10000;	/* 10 sec */
 
-/* Signal handlers */
-static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
-
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
 
@@ -88,22 +80,6 @@ static void StartupProcExit(int code, Datum arg);
  * --------------------------------
  */
 
-/* SIGUSR2: set flag to finish recovery */
-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();
-}
-
 /* SIGTERM: set flag to abort redo and exit */
 static void
 StartupProcShutdownHandler(SIGNAL_ARGS)
@@ -111,8 +87,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +106,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +134,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +153,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -205,6 +166,8 @@ StartupProcExit(int code, Datum arg)
 	/* Shutdown the recovery environment */
 	if (standbyState != STANDBY_DISABLED)
 		ShutdownRecoveryTransactionEnvironment();
+
+	ProcGlobal->startupProc = INVALID_PROC_NUMBER;
 }
 
 
@@ -220,20 +183,23 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	MyBackendType = B_STARTUP;
 	AuxiliaryProcessMainCommon();
 
+	/*
+	 * Advertise our proc number so that backends can wake us up, when the
+	 * server is promoted or recovery is paused/resumed.
+	 */
+	ProcGlobal->startupProc = MyProcNumber;
+
 	/* Arrange to clean up at startup process exit */
 	on_shmem_exit(StartupProcExit, 0);
 
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
 	/*
 	 * Register timeouts needed for standby mode
@@ -242,6 +208,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 +242,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 +252,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 9293849d772..9eccb8a867a 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -273,16 +273,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -323,7 +321,7 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	whereToSendOutput = DestNone;
 
 	/*
-	 * Set up a reusable WaitEventSet object we'll use to wait for our latch,
+	 * Set up a reusable WaitEventSet object we'll use to wait for interrupts,
 	 * and (except on Windows) our socket.
 	 *
 	 * Unlike all other postmaster child processes, we'll ignore postmaster
@@ -333,9 +331,12 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * (including the postmaster).
 	 */
 	wes = CreateWaitEventSet(NULL, 2);
-	AddWaitEventToSet(wes, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch, NULL);
+	AddWaitEventToSet(wes, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD |
+					  INTERRUPT_WAIT_WAKEUP,	/* for log rotation */
+					  NULL);
 #ifndef WIN32
-	AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], NULL, NULL);
+	AddWaitEventToSet(wes, WL_SOCKET_READABLE, syslogPipe[0], 0, NULL);
 #endif
 
 	/* main worker loop */
@@ -351,14 +352,13 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 #endif
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * Process any requests or signals received recently.
 		 */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 
 			/*
@@ -1183,7 +1183,7 @@ pipeThread(void *arg)
 				 ftello(csvlogFile) >= Log_RotationSize * (pgoff_t) 1024) ||
 				(jsonlogFile != NULL &&
 				 ftello(jsonlogFile) >= Log_RotationSize * (pgoff_t) 1024))
-				SetLatch(MyLatch);
+				RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 		LeaveCriticalSection(&sysloggerSection);
 	}
@@ -1194,8 +1194,8 @@ pipeThread(void *arg)
 	/* if there's any data left then force it out now */
 	flush_pipe_input(logbuffer, &bytes_in_logbuffer);
 
-	/* set the latch to waken the main thread, which will quit */
-	SetLatch(MyLatch);
+	/* raise the interrupt to waken the main thread, which will quit */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	LeaveCriticalSection(&sysloggerSection);
 	_endthread();
@@ -1586,9 +1586,10 @@ RemoveLogrotateSignalFiles(void)
 }
 
 /* SIGUSR1: set flag to rotate logfile */
+/*  TODO: Use an interrupt flag for this */
 static void
 sigUsr1Handler(SIGNAL_ARGS)
 {
 	rotation_requested = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 1e7a98b02c3..317e1fcaf93 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -31,17 +31,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -146,7 +146,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);
@@ -244,16 +244,18 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 	 * Properly accept or ignore signals the postmaster might send us
 	 *
 	 * We have no particular use for SIGINT at the moment, but seems
-	 * reasonable to treat like SIGTERM.
+	 * reasonable to treat like SIGTERM. FIXME: Why is that reasonable?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit at any point if requested */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -313,10 +315,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) */
@@ -353,7 +353,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();
@@ -628,8 +628,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)
@@ -644,7 +644,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -853,27 +853,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();
 }
 
 /*
@@ -1012,7 +1002,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);
@@ -1499,7 +1489,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1537,7 +1527,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. */
@@ -1637,11 +1627,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1688,7 +1682,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,
@@ -1707,7 +1701,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 aae5d3c861c..f9fc3c1b194 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -101,16 +102,16 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	 * Properly accept or ignore signals the postmaster might send us
 	 *
 	 * We have no particular use for SIGINT at the moment, but seems
-	 * reasonable to treat like SIGTERM.
+	 * reasonable to treat like SIGTERM. XXX: is it really
+	 * reasonable/necessary?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -219,12 +220,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))
 		{
@@ -232,11 +233,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -260,9 +260,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 8a01f16a2ca..5ba9af006f5 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,10 +157,12 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
@@ -238,12 +240,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -706,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)
@@ -759,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);
@@ -805,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);
 			}
 		}
 		else
@@ -844,9 +831,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -871,16 +857,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(SIGTERM, die);
-	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
+	SetStandardInterruptHandlers();
 	BackgroundWorkerUnblockSignals();
 
 	/*
@@ -941,8 +929,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,21 +973,6 @@ ParallelApplyWorkerMain(Datum main_arg)
 	Assert(false);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process a single protocol message received from a single parallel apply
  * worker.
@@ -1065,7 +1037,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1075,6 +1047,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1098,8 +1073,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1190,14 +1163,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();
 		}
 
@@ -1262,13 +1236,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 3ed86480be2..d66ba0b6f5a 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -58,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;
@@ -183,7 +184,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -219,27 +219,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -473,6 +474,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
@@ -539,7 +541,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))
@@ -566,7 +567,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -589,13 +590,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -616,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -630,13 +632,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -663,7 +666,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);
@@ -672,8 +675,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -710,13 +714,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
@@ -738,7 +742,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.
  */
@@ -747,7 +751,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -815,7 +819,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);
@@ -847,6 +851,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;
 }
 
@@ -858,7 +863,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;
 }
 
 /*
@@ -1019,7 +1024,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);
@@ -1045,6 +1049,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1193,8 +1198,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);
 }
 
 /*
@@ -1208,14 +1217,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);
-	pqsignal(SIGTERM, die);
+	/* 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 +1268,7 @@ ApplyLauncherMain(Datum main_arg)
 		 * detection if one of the subscription enables retain_dead_tuples
 		 * option.
 		 */
+		ClearInterrupt(INTERRUPT_SUBSCRIPTION_CHANGE);
 		sublist = get_subscription_list();
 		foreach(lc, sublist)
 		{
@@ -1417,21 +1429,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);
 		}
 	}
 
@@ -1586,7 +1596,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 165f909b3ba..92c903d7f96 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 5ffe177f940..39a79d4eeab 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,9 +59,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -79,9 +80,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -104,7 +105,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1284,7 +1285,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;
@@ -1347,13 +1348,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1375,7 +1377,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1419,7 +1423,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
@@ -1465,13 +1469,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1479,7 +1486,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);
 
@@ -1491,16 +1498,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);
 
@@ -1515,7 +1522,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;
@@ -1592,19 +1599,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1744,7 +1747,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);
 
@@ -1783,7 +1786,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1800,7 +1803,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1808,8 +1811,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1817,13 +1820,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1908,7 +1912,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1986,7 +1990,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 19a3c21a863..1cef9a499e6 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -167,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;
@@ -218,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;
@@ -518,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.
 						 */
@@ -697,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 32725c48623..29ad2ac4f92 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -4053,11 +4054,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();
@@ -4176,11 +4174,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4201,23 +4199,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5888,9 +5885,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 143569dffe3..9742ddd1fd4 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -622,7 +622,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	pid_t		active_pid;
 
 	Assert(name != NULL);
 
@@ -685,7 +684,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -707,7 +705,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -1968,7 +1966,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1978,7 +1976,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		pid_t		active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2062,11 +2059,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2105,22 +2097,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2161,7 +2156,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3253,11 +3249,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))
@@ -3267,6 +3260,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a way to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index e7bee777532..52a1aff55ab 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -265,22 +266,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -294,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))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -314,9 +315,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.")));
@@ -325,11 +325,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,
@@ -337,7 +341,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -944,7 +948,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 5b3d7fcf3d7..4c92fd05508 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -248,15 +249,15 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	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 */
+
+	/*
+	 * FIXME: we don't react to query cancel, so we can ignore. There'd be no
+	 * harm in keeping the signal handler though, because we won't react to
+	 * INTERRUPT_QUERY_CANCEL anyway
+	 */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -440,9 +441,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 				/* Process any requests or signals received recently */
 				CHECK_FOR_INTERRUPTS();
 
-				if (ConfigReloadPending)
+				if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				{
-					ConfigReloadPending = false;
 					ProcessConfigFile(PGC_SIGHUP);
 					/* recompute wakeup times */
 					now = GetCurrentTimestamp();
@@ -515,25 +515,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -681,7 +683,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -713,8 +715,12 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 		}
 		SpinLockRelease(&walrcv->mutex);
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_TERMINATE |
+							 INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+							 0,
+							 WAIT_EVENT_WAL_RECEIVER_WAIT_START);
 	}
 
 	if (update_process_title)
@@ -1385,7 +1391,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 42e3e170bc0..56ca8f7389c 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"
@@ -332,7 +333,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 3f9a427f7c6..bf11d460ac6 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -201,15 +202,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_STOPPING = false;
-
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM.
+ * When set, the main loop is responsible for checking
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -282,7 +280,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -370,7 +368,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -738,8 +737,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();
@@ -773,7 +780,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)
@@ -972,12 +981,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1036,9 +1047,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1475,7 +1486,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1529,7 +1540,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1638,17 +1649,19 @@ 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);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -1658,8 +1671,9 @@ ProcessPendingWrites(void)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1762,7 +1776,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1849,14 +1863,13 @@ WalSndWaitForWal(XLogRecPtr loc)
 		TimestampTz now;
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -1869,7 +1882,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			XLogBackgroundFlush();
 
 		/*
@@ -1894,7 +1907,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1976,11 +1989,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2006,7 +2023,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2879,6 +2896,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2893,15 +2911,21 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
+
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
 		/* Process any requests or signals received recently */
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			SyncRepInitConfig();
 		}
@@ -2953,13 +2977,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);
 		}
 
@@ -3011,7 +3035,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3049,6 +3078,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3105,6 +3135,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3199,7 +3230,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3568,8 +3599,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3686,52 +3717,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3814,24 +3836,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SetLatch(), helping
-	 * walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3879,16 +3905,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 d2c9cd6f20a..64df384656b 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d7c144cd8f7..25f1800cd6d 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,22 +262,24 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
-			pgaio_debug_io(DEBUG4, staged_ios[i],
+			pgaio_debug_io(LOG, staged_ios[i],
 						   "choosing worker %d",
 						   worker);
 		}
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -393,20 +394,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 	MyBackendType = B_IO_WORKER;
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -454,11 +455,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -491,13 +492,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -568,18 +569,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 3bd86223abd..e76a2a59255 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3345,7 +3346,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);
@@ -3525,7 +3526,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3606,7 +3607,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3697,7 +3698,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);
 	}
@@ -6633,12 +6634,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other signals as well. We
+		 * take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e208457df27..e85e25faadf 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -359,6 +359,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	 * 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.
+	 *
+	 * FIXME: slightly outdated comment
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..989c90a8cbb 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -174,11 +175,11 @@ proc_exit_prepare(int code)
 	/*
 	 * 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.
+	 * these flags again, now that proc_exit_inprogress is set. FIXME: is that
+	 * so?
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/pmsignal.c b/src/backend/storage/ipc/pmsignal.c
index 4618820b337..5a510fbbd9c 100644
--- a/src/backend/storage/ipc/pmsignal.c
+++ b/src/backend/storage/ipc/pmsignal.c
@@ -23,6 +23,7 @@
 
 #include "miscadmin.h"
 #include "postmaster/postmaster.h"
+#include "port/atomics.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
@@ -68,6 +69,13 @@
 #define PM_CHILD_ACTIVE		2
 #define PM_CHILD_WALSENDER	3
 
+typedef struct PMChildSlotData
+{
+	sig_atomic_t state;
+
+	pg_atomic_uint32 pgprocno;
+} PMChildSlotData;
+
 /* "typedef struct PMSignalData PMSignalData" appears in pmsignal.h */
 struct PMSignalData
 {
@@ -75,20 +83,21 @@ struct PMSignalData
 	sig_atomic_t PMSignalFlags[NUM_PMSIGNALS];
 	/* global flags for signals from postmaster to children */
 	QuitSignalReason sigquit_reason;	/* why SIGQUIT was sent */
+
 	/* per-child-process flags */
-	int			num_child_flags;	/* # of entries in PMChildFlags[] */
-	sig_atomic_t PMChildFlags[FLEXIBLE_ARRAY_MEMBER];
+	int			num_child_slots;	/* # of entries in child_slots[] */
+	PMChildSlotData child_slots[FLEXIBLE_ARRAY_MEMBER];
 };
 
 /* PMSignalState pointer is valid in both postmaster and child processes */
 NON_EXEC_STATIC volatile PMSignalData *PMSignalState = NULL;
 
 /*
- * Local copy of PMSignalState->num_child_flags, only valid in the
+ * Local copy of PMSignalState->num_child_slots, only valid in the
  * postmaster.  Postmaster keeps a local copy so that it doesn't need to
  * trust the value in shared memory.
  */
-static int	num_child_flags;
+static int	num_child_slots;
 
 /*
  * Signal handler to be notified if postmaster dies.
@@ -131,9 +140,9 @@ PMSignalShmemSize(void)
 {
 	Size		size;
 
-	size = offsetof(PMSignalData, PMChildFlags);
+	size = offsetof(PMSignalData, child_slots);
 	size = add_size(size, mul_size(MaxLivePostmasterChildren(),
-								   sizeof(sig_atomic_t)));
+								   sizeof(PMChildSlotData)));
 
 	return size;
 }
@@ -153,8 +162,8 @@ PMSignalShmemInit(void)
 	{
 		/* initialize all flags to zeroes */
 		MemSet(unvolatize(PMSignalData *, PMSignalState), 0, PMSignalShmemSize());
-		num_child_flags = MaxLivePostmasterChildren();
-		PMSignalState->num_child_flags = num_child_flags;
+		num_child_slots = MaxLivePostmasterChildren();
+		PMSignalState->num_child_slots = num_child_slots;
 	}
 }
 
@@ -229,13 +238,14 @@ GetQuitSignalReason(void)
 void
 MarkPostmasterChildSlotAssigned(int slot)
 {
-	Assert(slot > 0 && slot <= num_child_flags);
+	Assert(slot > 0 && slot <= num_child_slots);
 	slot--;
 
-	if (PMSignalState->PMChildFlags[slot] != PM_CHILD_UNUSED)
+	if (PMSignalState->child_slots[slot].state != PM_CHILD_UNUSED)
 		elog(FATAL, "postmaster child slot is already in use");
 
-	PMSignalState->PMChildFlags[slot] = PM_CHILD_ASSIGNED;
+	pg_atomic_init_u32(&PMSignalState->child_slots[slot].pgprocno, (uint32) INVALID_PROC_NUMBER);
+	PMSignalState->child_slots[slot].state = PM_CHILD_ASSIGNED;
 }
 
 /*
@@ -250,7 +260,7 @@ MarkPostmasterChildSlotUnassigned(int slot)
 {
 	bool		result;
 
-	Assert(slot > 0 && slot <= num_child_flags);
+	Assert(slot > 0 && slot <= num_child_slots);
 	slot--;
 
 	/*
@@ -258,8 +268,9 @@ MarkPostmasterChildSlotUnassigned(int slot)
 	 * postmaster.c is such that this might get called twice when a child
 	 * crashes.  So we don't try to Assert anything about the state.
 	 */
-	result = (PMSignalState->PMChildFlags[slot] == PM_CHILD_ASSIGNED);
-	PMSignalState->PMChildFlags[slot] = PM_CHILD_UNUSED;
+	result = (PMSignalState->child_slots[slot].state == PM_CHILD_ASSIGNED);
+	PMSignalState->child_slots[slot].state = PM_CHILD_UNUSED;
+	pg_atomic_init_u32(&PMSignalState->child_slots[slot].pgprocno, (uint32) INVALID_PROC_NUMBER);
 	return result;
 }
 
@@ -270,10 +281,10 @@ MarkPostmasterChildSlotUnassigned(int slot)
 bool
 IsPostmasterChildWalSender(int slot)
 {
-	Assert(slot > 0 && slot <= num_child_flags);
+	Assert(slot > 0 && slot <= num_child_slots);
 	slot--;
 
-	if (PMSignalState->PMChildFlags[slot] == PM_CHILD_WALSENDER)
+	if (PMSignalState->child_slots[slot].state == PM_CHILD_WALSENDER)
 		return true;
 	else
 		return false;
@@ -291,15 +302,43 @@ RegisterPostmasterChildActive(void)
 {
 	int			slot = MyPMChildSlot;
 
-	Assert(slot > 0 && slot <= PMSignalState->num_child_flags);
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
 	slot--;
-	Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ASSIGNED);
-	PMSignalState->PMChildFlags[slot] = PM_CHILD_ACTIVE;
+	Assert(PMSignalState->child_slots[slot].state == PM_CHILD_ASSIGNED);
+	PMSignalState->child_slots[slot].state = PM_CHILD_ACTIVE;
 
 	/* Arrange to clean up at exit. */
 	on_shmem_exit(MarkPostmasterChildInactive, 0);
 }
 
+/*
+ */
+void
+RegisterPostmasterChildProcNumber(ProcNumber procno)
+{
+	int			slot = MyPMChildSlot;
+
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
+	slot--;
+	Assert(PMSignalState->child_slots[slot].state == PM_CHILD_ACTIVE);
+	Assert(pg_atomic_read_u32(&PMSignalState->child_slots[slot].pgprocno) == (uint32) INVALID_PROC_NUMBER);
+	pg_atomic_write_u32(&PMSignalState->child_slots[slot].pgprocno, (uint32) procno);
+}
+
+/*
+ */
+void
+UnregisterPostmasterChildProcNumber(ProcNumber procno)
+{
+	int			slot = MyPMChildSlot;
+
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
+	slot--;
+	Assert(PMSignalState->child_slots[slot].state == PM_CHILD_ACTIVE);
+	Assert(pg_atomic_read_u32(&PMSignalState->child_slots[slot].pgprocno) == (uint32) procno);
+	pg_atomic_write_u32(&PMSignalState->child_slots[slot].pgprocno, (uint32) INVALID_PROC_NUMBER);
+}
+
 /*
  * MarkPostmasterChildWalSender - mark a postmaster child as a WAL sender
  * process.  This is called in the child process, sometime after marking the
@@ -312,10 +351,10 @@ MarkPostmasterChildWalSender(void)
 
 	Assert(am_walsender);
 
-	Assert(slot > 0 && slot <= PMSignalState->num_child_flags);
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
 	slot--;
-	Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ACTIVE);
-	PMSignalState->PMChildFlags[slot] = PM_CHILD_WALSENDER;
+	Assert(PMSignalState->child_slots[slot].state == PM_CHILD_ACTIVE);
+	PMSignalState->child_slots[slot].state = PM_CHILD_WALSENDER;
 }
 
 /*
@@ -327,11 +366,11 @@ MarkPostmasterChildInactive(int code, Datum arg)
 {
 	int			slot = MyPMChildSlot;
 
-	Assert(slot > 0 && slot <= PMSignalState->num_child_flags);
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
 	slot--;
-	Assert(PMSignalState->PMChildFlags[slot] == PM_CHILD_ACTIVE ||
-		   PMSignalState->PMChildFlags[slot] == PM_CHILD_WALSENDER);
-	PMSignalState->PMChildFlags[slot] = PM_CHILD_ASSIGNED;
+	Assert(PMSignalState->child_slots[slot].state == PM_CHILD_ACTIVE ||
+		   PMSignalState->child_slots[slot].state == PM_CHILD_WALSENDER);
+	PMSignalState->child_slots[slot].state = PM_CHILD_ASSIGNED;
 }
 
 
@@ -430,3 +469,12 @@ PostmasterDeathSignalInit(void)
 	postmaster_possibly_dead = true;
 #endif							/* USE_POSTMASTER_DEATH_SIGNAL */
 }
+
+ProcNumber
+GetPMChildProcNumber(int slot)
+{
+	Assert(slot > 0 && slot <= PMSignalState->num_child_slots);
+	slot--;
+
+	return (ProcNumber) pg_atomic_read_u32(&PMSignalState->child_slots[slot].pgprocno);
+}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 423ca3aeaa1..148ae32e334 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"
@@ -3450,27 +3451,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3510,10 +3511,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;
 		}
@@ -3545,21 +3546,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 5d33559926a..b80426828b1 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -26,7 +27,7 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -34,28 +35,21 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
  * (or greater) appears in the pss_barrierGeneration flag of every process,
  * we know that the message has been received everywhere.
@@ -65,7 +59,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) */
@@ -105,7 +98,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -150,7 +142,6 @@ ProcSignalShmemInit(void)
 			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);
@@ -181,9 +172,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -232,9 +220,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;
@@ -269,73 +258,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
@@ -380,8 +302,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
@@ -392,25 +314,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;
@@ -470,23 +379,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.
  *
@@ -502,12 +394,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
@@ -639,68 +527,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -759,6 +586,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 3ce6068ac54..ab995093140 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * process interrupts are used for synchronization.  Only the sender may send,
  * and only the receiver may receive.  This is intended to allow a user
  * backend to communicate with worker backends that it has registered.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -44,10 +45,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.
@@ -112,7 +114,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
@@ -214,7 +216,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));
 }
 
 /*
@@ -232,7 +234,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));
 }
 
 /*
@@ -341,16 +343,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
@@ -539,7 +540,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;
 	}
 
@@ -557,16 +558,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)
@@ -619,8 +619,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)
 	{
@@ -895,7 +895,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +993,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
@@ -1001,7 +1001,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;
@@ -1009,17 +1009,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();
@@ -1054,7 +1055,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.
 			 */
@@ -1150,25 +1151,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);
 	}
 }
 
@@ -1250,14 +1255,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;
@@ -1293,7 +1302,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 6f7759cd720..8b8505053c3 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -112,6 +113,7 @@ pg_signal_backend(int pid, int sig)
 	 */
 
 	/* If we have setsid(), signal the backend's whole process group */
+	elog(LOG, "sending %d to %d (%s)", sig, pid, (sig == SIGTERM) ? "SIGTERM" : "other");
 #ifdef HAVE_SETSID
 	if (kill(-pid, sig))
 #else
@@ -204,12 +206,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..d0aede40ee9 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and will set the
- * processes latch. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * setting the process's latch.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,14 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
 		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
-		 * can just call AcceptInvalidationMessages() to do this.  If we
-		 * aren't, we start and immediately end a transaction; the call to
+		 * INTERRUPT_SINVAL_CATCHUP flag. (FIXME: actually it's already been
+		 * cleared) 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()
@@ -190,14 +149,15 @@ 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();
 		}
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -658,8 +666,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -670,8 +678,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index d83afbfb9d6..0eb51565bbe 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -596,7 +597,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
@@ -698,7 +699,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
@@ -746,7 +751,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:
@@ -772,9 +781,10 @@ cleanup:
  * We either resolve conflicts immediately or set a timeout to wake us at
  * the limit of our patience.
  *
- * Resolve conflicts by sending a PROCSIG signal to all backends to check if
- * they hold one of the buffer pins that is blocking Startup process. If so,
- * those backends will take an appropriate error action, ERROR or FATAL.
+ * Resolve conflicts by sending 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
@@ -838,9 +848,17 @@ 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 the signal
+	 * handler??
 	 */
-	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);
@@ -936,6 +954,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -945,6 +964,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -954,6 +974,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 772e350a0c0..e8a8d74380a 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -128,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;
+	uint32		interrupt_mask;
+	int			interrupt_pos;
 
 	/*
 	 * WL_EXIT_ON_PM_DEATH is converted to WL_POSTMASTER_DEATH, but this flag
@@ -167,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -183,9 +184,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
 
@@ -235,7 +243,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)
@@ -285,12 +293,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");
@@ -311,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -349,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -412,7 +426,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;
 
@@ -496,9 +511,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)
 		{
@@ -535,7 +550,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
@@ -553,10 +568,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.
@@ -566,7 +577,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, uint32 interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +591,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 */
@@ -608,10 +616,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)
@@ -645,14 +653,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, uint32 interruptMask)
 {
 	WaitEvent  *event;
 #if defined(WAIT_USE_KQUEUE)
@@ -682,40 +690,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +747,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)
@@ -794,9 +795,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)
@@ -858,9 +858,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;
@@ -886,8 +886,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 |
@@ -902,10 +901,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
 	{
@@ -985,10 +984,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)
 	{
@@ -1044,6 +1046,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);
 
@@ -1065,100 +1068,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		uint32		old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u32(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u32(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
+		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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u32(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1229,16 +1249,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1391,13 +1411,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1513,16 +1533,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1621,6 +1641,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
@@ -1726,19 +1755,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1783,7 +1808,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
@@ -1889,18 +1914,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)
 {
@@ -1917,7 +1941,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;
@@ -2004,7 +2028,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2014,12 +2037,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();
@@ -2027,12 +2049,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..f961054a4ba 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1584,7 +1585,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;
@@ -3617,7 +3622,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 2405b59b501..0be48ba796f 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -310,14 +311,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -462,6 +477,8 @@ InitProcess(void)
 				 errmsg("sorry, too many clients already")));
 	}
 	MyProcNumber = GetNumberFromPGProc(MyProc);
+	if (IsUnderPostmaster)
+		RegisterPostmasterChildProcNumber(MyProcNumber);
 
 	/*
 	 * Cross-check that the PGPROC is of the type we expect; if this were not
@@ -533,13 +550,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);
@@ -669,6 +681,8 @@ InitAuxiliaryProcess(void)
 
 	MyProc = auxproc;
 	MyProcNumber = GetNumberFromPGProc(MyProc);
+	if (IsUnderPostmaster)
+		RegisterPostmasterChildProcNumber(MyProcNumber);
 
 	/*
 	 * Initialize all fields of MyProc, except for those previously
@@ -703,13 +717,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -993,21 +1002,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1066,13 +1074,12 @@ AuxiliaryProcKill(int code, Datum arg)
 	ConditionVariableCancelSleep();
 
 	/* look at the equivalent ProcKill() code for comments */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	SpinLockAcquire(ProcStructLock);
 
@@ -1399,18 +1406,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
 	{
@@ -1456,9 +1463,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)
 			{
@@ -1664,8 +1672,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)
 	{
@@ -1702,7 +1710,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 links invalid.
  *
@@ -1731,7 +1739,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1883,14 +1891,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;
 }
 
@@ -1969,34 +1975,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 378c2a03f39..dd8b1fc10f5 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 b1accc68b95..fbed7d82e98 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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 94a7b839563..226a3f42534 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 06ae1e3edbc..cadb4bb134c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -336,8 +337,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -354,11 +353,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();
 
@@ -465,7 +476,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;
 }
@@ -489,104 +502,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).
  *
@@ -2911,7 +2826,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 */
@@ -3008,50 +2923,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-void
-StatementCancelHandler(SIGNAL_ARGS)
-{
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3068,22 +2959,85 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to check for a particular type of XXX
- * recovery conflict.  Runs in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	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)
 	{
@@ -3246,14 +3200,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,66 +3234,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
-
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
- *
- * If an interrupt condition is pending, and it's safe to service it,
- * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
- *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts
- * is guaranteed to clear the InterruptPending flag before returning.
- * (This is not the same as guaranteeing that it's still clear when we
- * return; another interrupt could have arrived.  But we promise that
- * any pre-existing one will have been serviced.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3388,65 +3307,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		CheckClientConnectionPending = false;
-
-		/*
-		 * Check for lost connection and re-arm, if still configured, but not
-		 * if we've arrived back at DoingCommandRead state.  We don't want to
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * Check for lost connection and re-arm, if still configured, but not if
+	 * we've arrived back at DoingCommandRead state.  We don't want to wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
+
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
+
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
 
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
-	{
-		/*
-		 * Re-arm InterruptPending so that we process the cancel request as
-		 * soon as we're done reading the message.  (XXX this is seriously
-		 * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we
-		 * can't use that macro directly as the initial test in this function,
-		 * meaning that this code also creates opportunities for other bugs to
-		 * appear.)
-		 */
-		InterruptPending = true;
-	}
-	else if (QueryCancelPending)
+
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3499,76 +3408,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * interrupt.  We need to unset the flag before the injection point,
-		 * otherwise we could loop in interrupts checking.
-		 */
-		IdleInTransactionSessionTimeoutPending = false;
-		if (IdleInTransactionSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4232,9 +4131,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4247,13 +4150,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4267,7 +4182,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4276,11 +4198,31 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4448,11 +4390,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 */
@@ -4633,7 +4578,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);
 
 				/*
@@ -4722,6 +4667,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)
 		 */
@@ -4751,22 +4718,22 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 4543626ffc8..9120feab7be 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -295,14 +295,7 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 	}
 
-	procNumber = GetNumberFromPGProc(proc);
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
+	SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc));
 
 	PG_RETURN_BOOL(true);
 }
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 32a787d7df7..9f660363921 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,16 +346,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;
 
@@ -374,11 +374,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 8deb2369471..df7657031b2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1739,10 +1739,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 aa530d3685e..7d24db950db 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -526,7 +526,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,6 @@
 
 ProtocolVersion FrontendProtocol;
 
-volatile sig_atomic_t InterruptPending = false;
-volatile sig_atomic_t QueryCancelPending = false;
-volatile sig_atomic_t ProcDiePending = false;
-volatile sig_atomic_t CheckClientConnectionPending = false;
-volatile sig_atomic_t ClientConnectionLost = false;
-volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t TransactionTimeoutPending = false;
-volatile sig_atomic_t IdleSessionTimeoutPending = false;
-volatile sig_atomic_t ProcSignalBarrierPending = false;
-volatile sig_atomic_t LogMemoryContextPending = false;
-volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 QueryCancelHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
 
 int			MyProcPid;
 pg_time_t	MyStartTime;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 608f7ad7abb..8f5cb8648e2 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -143,25 +139,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -201,10 +184,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.
@@ -225,48 +207,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 3f401faf3de..b69cdc62743 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"
@@ -1357,20 +1358,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 ?
+	 */
 }
 
 /*
@@ -1379,51 +1379,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
- * to call this function if we see LogMemoryContextPending set.
- * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
- * the target process for logging of memory contexts is a backend.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/include/Makefile b/src/include/Makefile
index 4ef060e9050..b3f5cd98033 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 01bdf2bec1f..21b0b0a1ec7 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -53,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;
 
@@ -70,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..445ab54c045
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,364 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+#include "storage/waiteventset.h"	/* WL_* are defined in waiteventset.h */
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+enum InterruptType
+{
+	/*
+	 * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+	 * a process, which don't need a dedicated interrupt bit.
+	 */
+	INTERRUPT_WAIT_WAKEUP = 0x00000001,
+
+
+	/***********************************************************************
+	 * 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().
+	 */
+	INTERRUPT_TERMINATE = 0x00000002,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_QUERY_CANCEL = 0x00000004,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_RECOVERY_CONFLICT = 0x00000008,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_CONFIG_RELOAD = 0x00000010,
+
+	/*
+	 * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+	 */
+	INTERRUPT_LOG_MEMORY_CONTEXT = 0x00000020,
+
+	/*
+	 * procsignal global barrier interrupt
+	 */
+	INTERRUPT_BARRIER = 0x00000040,
+
+
+	/***********************************************************************
+	 * 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 */
+	INTERRUPT_TRANSACTION_TIMEOUT = 0x00000080,
+	INTERRUPT_IDLE_SESSION_TIMEOUT = 0x00000100,
+	INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT = 0x00000200,
+	INTERRUPT_CLIENT_CHECK_TIMEOUT = 0x00000400,
+
+	/* Raised by timer while idle, to send a stats update */
+	INTERRUPT_IDLE_STATS_TIMEOUT = 0x00000800,
+
+	/* Raised synchronously when the client connection is lost */
+	INTERRUPT_CLIENT_CONNECTION_LOST = 0x00001000,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_ASYNC_NOTIFY = 0x00002000,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_SINVAL_CATCHUP = 0x00004000,
+
+	/* Message from a cooperating parallel backend or apply worker */
+	INTERRUPT_PARALLEL_MESSAGE = 0x00008000,
+
+
+	/***********************************************************************
+	 * Process-specific interrupts
+	 *
+	 * Some processes need dedicated interrupts for various purposes.  Ignored
+	 * by other processes.
+	 ***********************************************************************/
+
+	/* ask walsenders to prepare for shutdown  */
+	INTERRUPT_WALSND_INIT_STOPPING = 0x00010000,
+
+	/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+	INTERRUPT_WALSND_STOP = 0x00020000,
+
+	/*
+	 * 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.
+	 */
+	INTERRUPT_WAL_ARRIVED = 0x00040000,
+
+	/* Wake up startup process to check for the promotion signal file */
+	INTERRUPT_CHECK_PROMOTE = 0x00080000,
+
+	/* sent to logical replication launcher, when a subscription changes */
+	INTERRUPT_SUBSCRIPTION_CHANGE = 0x00100000,
+
+	/* Graceful shutdown request for a parallel apply worker */
+	INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER = 0x00200000,
+
+	/*
+	 * Perform one last checkpoint, then shut down. Only used in the
+	 * checkpointer process.
+	 */
+	INTERRUPT_SHUTDOWN_XLOG = 0x00400000,
+
+	INTERRUPT_SHUTDOWN_PGARCH = 0x00800000,
+
+	/*
+	 * This is sent to the autovacuum launcher when an autovacuum worker exits
+	 */
+	INTERRUPT_AUTOVACUUM_WORKER_FINISHED = 0x01000000,
+
+
+	/***********************************************************************
+	 * End of interrupt bits
+	 ***********************************************************************/
+
+	/*
+	 * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+	 * waiting for an interrupt. If set, the backend needs to be woken up when
+	 * a bit in the pending interrupts mask is set. It's used internally by
+	 * the interrupt machinery, and cannot be used directly in the public
+	 * functions. It's named differently to distinguish it from the actual
+	 * interrupt flags.
+	 */
+	SLEEPING_ON_INTERRUPTS = 0x80000000,
+
+	/*
+	 * NOTE: InterruptTypes must fit in a 32-bit bitmask. (If we had efficient
+	 * 64-bit atomics on all platforms, we could easily go up to 64 bits)
+	 */
+};
+
+typedef uint32 InterruptMask;
+
+extern PGDLLIMPORT pg_atomic_uint32 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u32(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u32(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions (TODO: not implemented) */
+extern void AllocateDynamicInterrupt();
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 412bc9758fb..35fbff9a763 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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 c62fffb5998..3ba2b82b3a8 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -17,7 +17,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/meson.build b/src/include/meson.build
index b940c5cd3d6..d0cfcaec84b 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 db559b39c4d..44942bb948d 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,132 +28,15 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
-
-#define InvalidPid				(-1)
-
-
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/interrupt.h"
 #endif
 
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
+#define InvalidPid				(-1)
 
 
 /*****************************************************************************
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
-
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TableSyncWorkerMain(Datum main_arg);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/slot.h b/src/include/replication/slot.h
index 72f8be629f3..4b4709f6e2c 100644
--- a/src/include/replication/slot.h
+++ b/src/include/replication/slot.h
@@ -215,7 +215,7 @@ typedef struct ReplicationSlot
 	/* is somebody performing io on this slot? */
 	LWLock		io_in_progress_lock;
 
-	/* Condition variable signaled when active_pid changes */
+	/* Condition variable signaled when active_proc changes */
 	ConditionVariable active_cv;
 
 	/* all the remaining data is only used for logical slots */
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if
+								 * not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index c1285fdd1bc..4b6449e974a 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index fbdadc86959..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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 */
-
-/*
- * 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/pmsignal.h b/src/include/storage/pmsignal.h
index 206fb78f8a5..8bfed7ee680 100644
--- a/src/include/storage/pmsignal.h
+++ b/src/include/storage/pmsignal.h
@@ -24,6 +24,8 @@
 #include <sys/procctl.h>
 #endif
 
+#include "storage/procnumber.h"
+
 /*
  * Reasons for signaling the postmaster.  We can cope with simultaneous
  * signals for different reasons.  If the same reason is signaled multiple
@@ -76,6 +78,9 @@ extern void MarkPostmasterChildSlotAssigned(int slot);
 extern bool MarkPostmasterChildSlotUnassigned(int slot);
 extern bool IsPostmasterChildWalSender(int slot);
 extern void RegisterPostmasterChildActive(void);
+extern void RegisterPostmasterChildProcNumber(ProcNumber procno);
+extern void UnregisterPostmasterChildProcNumber(ProcNumber procno);
+extern ProcNumber GetPMChildProcNumber(int slot);
 extern void MarkPostmasterChildWalSender(void);
 extern bool PostmasterIsAliveInternal(void);
 extern void PostmasterDeathSignalInit(void);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 090471c1144..71ac77f96e0 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -17,7 +17,6 @@
 #include "access/clog.h"
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -189,9 +188,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -235,15 +231,6 @@ struct PGPROC
 
 	bool		isRegularBackend;	/* true if it's a regular backend. */
 
-	/*
-	 * While in hot standby mode, shows that a conflict signal has been sent
-	 * for the current transaction. Set/cleared while holding ProcArrayLock,
-	 * though not required. Accessed without lock, if needed.
-	 *
-	 * This is a bitmask of RecoveryConflictReasons
-	 */
-	pg_atomic_uint32 pendingRecoveryConflicts;
-
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
@@ -335,6 +322,23 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint32 pendingInterrupts;
+
+	/*
+	 * While in hot standby mode, shows that a conflict signal has been sent
+	 * for the current transaction. Set/cleared while holding ProcArrayLock,
+	 * though not required. Accessed without lock, if needed.
+	 *
+	 * This is a bitmask of RecoveryConflictReasons.
+	 */
+	pg_atomic_uint32 pendingRecoveryConflicts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -437,6 +441,8 @@ typedef struct PROC_HDR
 	 */
 	ProcNumber	walwriterProc;
 	ProcNumber	checkpointerProc;
+	ProcNumber	walreceiverProc;
+	ProcNumber	startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -520,9 +526,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..6f67afae18f 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,13 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +69,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+struct ResourceOwnerData;
+
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  struct Latch *latch, void *user_data);
+							  uint32 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint32 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 11ab1717a16..5d47643eb77 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index d574a686b42..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
@@ -87,7 +90,9 @@ pg_numa_query_pages(int pid, unsigned long count, void **pages, int *status)
 		unsigned long count_chunk = Min(count - next,
 										NUMA_QUERY_CHUNK_SIZE);
 
+#ifndef FRONTEND
 		CHECK_FOR_INTERRUPTS();
+#endif
 
 		/*
 		 * Bail out if any of the chunks errors out (ret<0). We ignore (ret>0)
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 a0aec04d994..777d00283da 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index ba2fd746d73..d74ed414839 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -133,6 +134,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;
@@ -222,8 +224,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)
@@ -263,6 +263,9 @@ wait_for_workers_to_become_ready(worker_state *wstate,
 	{
 		int			workers_ready;
 
+		/* Clear the interrupt flag so we don't spin. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+
 		/* If all the workers are ready, we have succeeded. */
 		SpinLockAcquire(&hdr->mutex);
 		workers_ready = hdr->workers_ready;
@@ -285,11 +288,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 fe1794c6077..d8675ac1423 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "varatt.h"
 
@@ -170,6 +170,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
@@ -234,13 +236,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 368e4f3f234..c75b5edaafa 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,7 +19,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -52,15 +52,12 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
-	 * Establish signal handlers.
-	 *
-	 * We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
-	 * it would a normal user backend.  To make that happen, we use die().
+	 * The standard interrupt and signal handlers are OK for us, in particular
+	 * we want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
+	 * it would a normal user backend. Just unblock signals.
 	 */
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
@@ -122,13 +119,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 d1e4a2bd952..8f9fa9e1834 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -155,11 +153,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -213,26 +210,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
@@ -366,7 +362,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.
@@ -420,8 +415,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6990f1bd6f7..b43c101f916 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1324,6 +1324,8 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
+InterruptType
 Interval
 IntervalAggState
 IntoClause
@@ -1561,7 +1563,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2025,6 +2026,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2026-02-13 17:11           ` Heikki Linnakangas <[email protected]>
  2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2026-02-13 17:11 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 29/01/2026 03:05, Heikki Linnakangas wrote:
> Here's a new rebased and massively re-worked patch.
> 
> The patches are split differently than in the previous version:
> - Patches 0001-0005 are just refactoring around recovery conflicts which 
> I posted on a separate thread [1]. Please review and comment there.
> - Patches 0006 and 0007 are small cleanups that could be applied early 
> (after updating the docs, as noted in TODO comment there).
> - All the interesting bits for this thread are in the last, massive patch.
> 
> To review this, I suggest starting from the new src/backend/ipc/ 
> README.md file. It gives a good overview of the mechanism (or if it 
> doesn't, that's valuable feedback :-) ). It also contains a bunch of 
> Open Questions at the bottom; I'd love to hear opinions and ideas on those.

New version attached. Lots of little cleanups, and a few more notable 
changes:

- Switched to 64-bit interrupt masks. This gives more headroom for 
extensions to reserve their own custom interrupts

- I mostly gave up on sending interrupts from postmaster to child 
processes. So postmaster still uses kill() to tell child processes to 
exit or do other things. There's a TODO section in the README about 
that, but this is fine for now.

- Implemented a RequestAddinInterrupt() function for extensions to 
request interrupt bits. (TODO: use it in one of the example extensions, 
to show how to use it and to test that it works)

> On 15/07/2025 18:50, Andres Freund wrote:
>>> @@ -205,6 +206,8 @@ StartupProcExit(int code, Datum arg)
>>>      /* Shutdown the recovery environment */
>>>      if (standbyState != STANDBY_DISABLED)
>>>          ShutdownRecoveryTransactionEnvironment();
>>> +
>>> +    ProcGlobal->startupProc = INVALID_PROC_NUMBER;
>>>  }
>>
>>
>> What if we instead had a ProcGlobal->auxProc[auxproxtype]? We have 
>> different
>> versions of this for different types auf aux processes, which doesn't 
>> really
>> make sense.
> 
> I like that idea, but didn't try it yet.

The attached patches move a little in that direction. There's no array 
like that, but I moved the responsibility of setting those 
startupProc/walreceiverProc/checkpointerProc fields to 
InitAuxiliaryProcess(), so there's now a clear pattern to copy if other 
processes need to be advertised like that.

- Heikki

Attachments:

  [text/x-patch] 0001-Ignore-SIGINT-in-walwriter-and-walsummarizer.patch (3.1K, ../../[email protected]/2-0001-Ignore-SIGINT-in-walwriter-and-walsummarizer.patch)
  download | inline diff:
From 3a81c457dc2b7daf89b8cdf4f058c1aaa52342da Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 13:19:47 +0200
Subject: [PATCH 1/5] Ignore SIGINT in walwriter and walsummarizer

Previously, SIGINT was treated the same as SIGTERM in walwriter and
walsummarizer. That decision goes back to when the walwriter process
was introduced (commit ad4295728e04), and was later copied to
walsummarizer. It was a pretty arbitrary decision back then, and we
haven't adopted that convention in all the other processes that have
been introduced later.

Summary of how other processes respond to SIGINT:
- Autovacuum launcher: Cancel the current iteration of launching
- bgworker: Ignore (unless connected to a database)
- checkpointer: Request shutdown checkpoint
- pgarch: Ignore
- startup process: Ignore
- walreceiver: Ignore
- IO worker: die()

IO workers are a notable exception in that they exit on SIGINT, and
there's a documented reason for that: IO workers ignore SIGTERM, so
SIGINT provides a way to manually kill them. (They do respond to
SIGUSR2, though, like all the other processes that we don't want to
exit immediately on SIGTERM on operating system shutdown.)

To make this a little more consistent, ignore SIGINT in walwriter and
walsummarizer. They have no "query" to cancel, and they react to
SIGTERM just fine.
---
 src/backend/postmaster/walsummarizer.c | 5 +----
 src/backend/postmaster/walwriter.c     | 5 +----
 2 files changed, 2 insertions(+), 8 deletions(-)

diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 2d8f57099fd..742137edad6 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -241,12 +241,9 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 
 	/*
 	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * We have no particular use for SIGINT at the moment, but seems
-	 * reasonable to treat like SIGTERM.
 	 */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
 	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 23e79a32345..7c0e2809c17 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -98,12 +98,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 
 	/*
 	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * We have no particular use for SIGINT at the moment, but seems
-	 * reasonable to treat like SIGTERM.
 	 */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SignalHandlerForShutdownRequest);
+	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
 	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGALRM, SIG_IGN);
-- 
2.47.3



  [text/x-patch] 0002-Centralize-resetting-SIGCHLD-handler.patch (9.7K, ../../[email protected]/3-0002-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From 3cabe90143342408a964b7a05e3c212051a71e20 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Jan 2026 20:22:43 +0200
Subject: [PATCH 2/5] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6fde740465f..89c2b4f9500 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -411,7 +411,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1422,7 +1421,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 261ccd3f59c..d4031ac3787 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -795,7 +795,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0956bd39a85..dbd670c16cc 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -108,11 +108,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e03c19123bc..03aefa5b670 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -220,11 +220,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 82731e452fc..10a5ef9e15c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -237,9 +237,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cdbe53dd262..330c10ddb0b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -234,11 +234,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 86c5e376b40..8515bfb9653 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -283,11 +283,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 742137edad6..4e4eed4d076 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -263,11 +263,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 7c0e2809c17..8ca7b07e947 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -108,11 +108,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 062a08ccb88..5e1ee7e4728 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1536,7 +1536,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 10e64a7d1f4..352a3f31e95 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -257,9 +257,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2cde8ebc729..5acc8ee3ac8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3743,9 +3743,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 21de158adbb..b88ce440b1d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4285,13 +4285,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
-									 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 03f6c8479f2..fadf856d9bc 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -142,6 +142,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -154,6 +163,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] 0003-Use-standard-die-handler-for-SIGTERM-in-bgworkers.patch (4.1K, ../../[email protected]/4-0003-Use-standard-die-handler-for-SIGTERM-in-bgworkers.patch)
  download | inline diff:
From 63d1a57f4906a924c426def4e1a7f27a71611b28 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 20 Jan 2026 16:57:25 +0200
Subject: [PATCH 3/5] Use standard die() handler for SIGTERM in bgworkers

---
 doc/src/sgml/bgworker.sgml                       |  2 ++
 src/backend/access/transam/parallel.c            |  1 -
 src/backend/postmaster/bgworker.c                | 16 +---------------
 .../replication/logical/applyparallelworker.c    |  1 -
 src/backend/replication/logical/launcher.c       |  1 -
 src/backend/replication/logical/worker.c         |  1 -
 6 files changed, 3 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 4699ef6345f..b239e38aaae 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -254,6 +254,8 @@ typedef struct BackgroundWorker
    <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.
+
+   TODO document that: you should call CHECK_FOR_INTERRUPTS(), to react promptly to terminate request (SIGTERM)
   </para>
 
   <para>
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index fe00488487d..44786dc131f 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1327,7 +1327,6 @@ ParallelWorkerMain(Datum main_arg)
 	InitializingParallelWorker = true;
 
 	/* Establish signal handlers. */
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/* Determine and set our parallel worker number. */
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index d4031ac3787..133af5aee97 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -718,20 +718,6 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
 	return true;
 }
 
-/*
- * Standard SIGTERM handler for background workers
- */
-static void
-bgworker_die(SIGNAL_ARGS)
-{
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
-
-	ereport(FATAL,
-			(errcode(ERRCODE_ADMIN_SHUTDOWN),
-			 errmsg("terminating background worker \"%s\" due to administrator command",
-					MyBgworkerEntry->bgw_type)));
-}
-
 /*
  * Main entry point for background worker processes.
  */
@@ -787,7 +773,7 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, bgworker_die);
+	pqsignal(SIGTERM, die);
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGHUP, SIG_IGN);
 
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 8a01f16a2ca..1730ace5490 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -879,7 +879,6 @@ ParallelApplyWorkerMain(Datum main_arg)
 	 * receiving SIGTERM.
 	 */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 	BackgroundWorkerUnblockSignals();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 3ed86480be2..e6112e11ec2 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1213,7 +1213,6 @@ ApplyLauncherMain(Datum main_arg)
 
 	/* Establish signal handlers. */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 32725c48623..75af3a71ca8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5890,7 +5890,6 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	/* Setup signal handling */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
-- 
2.47.3



  [text/x-patch] 0004-Refactor-how-some-aux-processes-advertise-their-Proc.patch (9.6K, ../../[email protected]/5-0004-Refactor-how-some-aux-processes-advertise-their-Proc.patch)
  download | inline diff:
From c18137939a0d2561fedc247a3f3135355923ffa8 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 14:23:35 +0200
Subject: [PATCH 4/5] Refactor how some aux processes advertise their
 ProcNumber

This moves the responsibility of setting the
ProcGlobal->walrewriterProc and checkpointerProc fields to
InitAuxiliaryProcess. Also switch to the same pattern to advertise the
autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
field in shared memory. This can easily be extended to other aux
processes in the future, if other processes need to find them.

Switch to pg_atomic_uint32 for the fields. Seems easier to reason
about than volatile pointers. There was some precedence for that, as
were already using pg_atomic_uint32 for the procArrayGroupFirst and
clogGroupFirst fields, which also store ProcNumbers.

TODO: could also replace WalRecv->procno with this
---
 src/backend/access/transam/xlog.c     |  3 +--
 src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
 src/backend/postmaster/checkpointer.c | 14 ++++---------
 src/backend/postmaster/walwriter.c    |  6 ------
 src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
 src/include/storage/proc.h            |  7 ++++---
 6 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13ec6225b85..8e2e4659974 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2653,8 +2653,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 
 	if (wakeup)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	walwriterProc = procglobal->walwriterProc;
+		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89c2b4f9500..0a730fb5b45 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -277,7 +277,6 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -293,7 +292,6 @@ typedef struct AutoVacuumWorkItem
 typedef struct
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -563,8 +561,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		proc_exit(0);			/* done */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
-
 	/*
 	 * Create the initial database list.  The invariant we want this list to
 	 * keep is that it's ordered by decreasing next_worker.  As soon as an
@@ -798,8 +794,6 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
-
 	proc_exit(0);				/* done */
 }
 
@@ -1529,6 +1523,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (AutoVacuumShmem->av_startingWorker != NULL)
 	{
+		ProcNumber	launcherProc;
+
 		MyWorkerInfo = AutoVacuumShmem->av_startingWorker;
 		dbid = MyWorkerInfo->wi_dboid;
 		MyWorkerInfo->wi_proc = MyProc;
@@ -1547,8 +1543,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
+		if (launcherProc != INVALID_PROC_NUMBER)
+		{
+			int			pid = GetPGProcByNumber(launcherProc)->pid;
+
+			if (pid != 0)
+				kill(pid, SIGUSR2);
+		}
 	}
 	else
 	{
@@ -3391,7 +3393,6 @@ AutoVacuumShmemInit(void)
 
 		Assert(!found);
 
-		AutoVacuumShmem->av_launcherpid = 0;
 		dclist_init(&AutoVacuumShmem->av_freeWorkers);
 		dlist_init(&AutoVacuumShmem->av_runningWorkers);
 		AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 03aefa5b670..978a7d0e649 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,6 +47,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
@@ -343,12 +344,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	UpdateSharedMemoryConfig();
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->checkpointerProc = MyProcNumber;
-
 	/*
 	 * Loop until we've been asked to write the shutdown checkpoint or
 	 * terminate.
@@ -1120,7 +1115,7 @@ RequestCheckpoint(int flags)
 	for (ntries = 0;; ntries++)
 	{
 		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 		if (checkpointerProc == INVALID_PROC_NUMBER)
 		{
@@ -1261,8 +1256,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	/* ... but not till after we release the lock */
 	if (too_full)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
@@ -1543,7 +1537,7 @@ void
 WakeupCheckpointer(void)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
-	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
 		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 8ca7b07e947..1851417ad1b 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -200,12 +200,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	hibernating = false;
 	SetWalWriterSleeping(false);
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->walwriterProc = MyProcNumber;
-
 	/*
 	 * Loop forever
 	 */
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index fd8318bdf3d..ba7eaed1a13 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -211,8 +211,9 @@ InitProcGlobal(void)
 	dlist_init(&ProcGlobal->bgworkerFreeProcs);
 	dlist_init(&ProcGlobal->walsenderFreeProcs);
 	ProcGlobal->startupBufferPinWaitBufId = -1;
-	ProcGlobal->walwriterProc = INVALID_PROC_NUMBER;
-	ProcGlobal->checkpointerProc = INVALID_PROC_NUMBER;
+	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->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -709,6 +710,14 @@ InitAuxiliaryProcess(void)
 	 */
 	PGSemaphoreReset(MyProc->sem);
 
+	/* Some aux processes are also advertised in ProcGlobal */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
+	if (MyBackendType == B_WAL_WRITER)
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
+	if (MyBackendType == B_CHECKPOINTER)
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+
 	/*
 	 * Arrange to clean up at process exit.
 	 */
@@ -1053,6 +1062,23 @@ AuxiliaryProcKill(int code, Datum arg)
 	SwitchBackToLocalLatch();
 	pgstat_reset_wait_event_storage();
 
+	/* If this was one of aux processes advertised in ProcGlobal, clear it */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->avLauncherProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_WAL_WRITER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walwriterProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_CHECKPOINTER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	}
+
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 23e5cd98161..1f1ab7d02ca 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,11 +445,12 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 clogGroupFirst;
 
 	/*
-	 * Current slot numbers of some auxiliary processes. There can be only one
+	 * Current proc numbers of some auxiliary processes. There can be only one
 	 * of each of these running at a time.
 	 */
-	ProcNumber	walwriterProc;
-	ProcNumber	checkpointerProc;
+	pg_atomic_uint32 avLauncherProc;
+	pg_atomic_uint32 walwriterProc;
+	pg_atomic_uint32 checkpointerProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
-- 
2.47.3



  [text/x-patch] 0005-Replace-Latches-with-Interrupts.patch (499.0K, ../../[email protected]/6-0005-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 128ff4fe457d986cf38edb5e286aacc2923ab82d Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Feb 2026 17:07:28 +0200
Subject: [PATCH 5/5] Replace Latches with Interrupts

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

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

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

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

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

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

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() clears the interrupts bits that it
processes, the caller is no longer required to do ClearInterrupt (or
ResetLatch) for those bits. If you wake up for other interrupts that
are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear
those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_WAIT_WAKEUP interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

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

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

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

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/[email protected]
  Discussion: https://www.postgresql.org/message-id/[email protected]

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

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

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

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

XXX: original text from Thomas's patch
--------------------------------------

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

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

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 89e187425cc..6be5ca9c183 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -170,10 +168,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. */
@@ -222,27 +223,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
 		{
@@ -266,14 +267,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);
 	}
 
 	/*
@@ -423,7 +423,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;
 
 		/*
@@ -444,7 +444,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)));
@@ -504,8 +504,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. */
@@ -923,9 +922,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),
@@ -958,9 +954,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 487a1a23170..3bf73e4d2a9 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -829,8 +829,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);
@@ -1483,7 +1483,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))
@@ -1578,7 +1578,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))
@@ -1686,12 +1686,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 				pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 			/* Sleep until there's something to do */
-			wc = WaitLatchOrSocket(MyLatch,
-								   WL_LATCH_SET | WL_SOCKET_READABLE |
-								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								   PQsocket(conn),
-								   cur_timeout, pgfdw_we_cleanup_result);
-			ResetLatch(MyLatch);
+			wc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_INTERRUPT | WL_SOCKET_READABLE |
+									   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									   PQsocket(conn),
+									   cur_timeout, pgfdw_we_cleanup_result);
 
 			CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 60d90329a65..eb925fda39e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index b239e38aaae..67a10053bbc 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
@@ -250,7 +261,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.
@@ -283,10 +294,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 05642dc02e3..98819087871 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..e8e84e248ce 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 436e54f2066..426259935e7 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 7a6b177977b..55b106d340d 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index d205093e21d..da2c946c7d1 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 11a6674a10b..2a667d65839 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index dfffce3e396..9fc03134804 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 11b214eb99b..9e236799f2e 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 632c2427952..9cb4b766795 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 4be267ff657..ae7e7993c8b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,6 +143,7 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
@@ -3300,11 +3301,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 3047bd46def..da1952e9412 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..70a4e4ee00a 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 44786dc131f..65c467f25f9 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -114,9 +115,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -126,9 +124,6 @@ static FixedParallelState *MyFixedParallelState;
 /* List of active parallel contexts. */
 static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list);
 
-/* Backend-local copy of data from FixedParallelState. */
-static pid_t ParallelLeaderPid;
-
 /*
  * List of internal parallel worker entry points.  We need this for
  * reasons explained in LookupParallelWorkerFunction(), below.
@@ -242,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -611,7 +606,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.
@@ -766,16 +760,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();
+				}
 			}
 		}
 
@@ -884,15 +883,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1033,21 +1033,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1082,9 +1067,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1326,7 +1309,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. */
@@ -1362,8 +1348,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));
 
@@ -1379,8 +1364,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
@@ -1619,9 +1603,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..5592df30f8d 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index eabc4d48208..2477618e870 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e2e4659974..b6f43b359c4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2656,7 +2656,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);
 	}
 }
 
@@ -9509,11 +9509,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2efe4105efb..8c74eceec18 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -24,11 +24,11 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.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"
@@ -718,17 +718,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index 4fc37a031d9..2d4eb4667f1 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -322,23 +323,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.
 	 */
@@ -473,7 +457,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -547,13 +530,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).
@@ -1654,13 +1630,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);
 }
 
 /*
@@ -1790,8 +1759,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
@@ -1820,8 +1789,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))
 			{
@@ -1842,9 +1811,8 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2977,7 +2945,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;
 
@@ -3054,8 +3022,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)
@@ -3063,11 +3031,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;
 
@@ -3088,10 +3061,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;
 }
@@ -3769,16 +3744,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;
@@ -4044,11 +4019,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;
 				}
 
@@ -4064,11 +4040,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 */
@@ -4500,11 +4473,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4542,7 +4515,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);
 }
 
 /*
@@ -4746,7 +4722,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 d286ff63123..efe08c4ebfe 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -68,7 +68,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -241,9 +241,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
@@ -298,14 +298,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);
 }
@@ -364,7 +363,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.
@@ -376,7 +375,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);
@@ -434,8 +433,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
@@ -447,8 +447,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index e112eed7485..e49fdc1efa2 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 657c591618d..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  sets the process's latch, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  main loop in PostgresBackend()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,6 +166,7 @@
 #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"
@@ -177,7 +174,6 @@
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -288,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -318,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -527,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. ProcessNotifyInterrupt() will then be called whenever it's safe to
- * actually deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -552,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1265,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1360,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1386,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1618,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1638,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2242,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2262,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2293,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2304,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2330,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2340,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2360,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2371,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, signalProcnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 }
 
@@ -2530,39 +2496,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted
- *		via the process's latch, and this routine will get called.
- *		If we are truly idle (ie, *not* inside a transaction block),
- *		process the incoming notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if the interrupt occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -2571,12 +2510,13 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
-	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2686,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3050,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 94d6f415a06..5005030cd7b 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -269,9 +269,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)
@@ -303,7 +307,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03932f45c8a..cc34f40f2ac 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2430,8 +2430,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2440,9 +2439,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();
 	}
@@ -2529,8 +2528,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 7138dc692c6..166b6f5eec6 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
+#include "utils/resowner.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1043,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;
@@ -1067,8 +1067,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,
@@ -1078,8 +1078,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)
@@ -1122,14 +1122,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 4105f1d1968..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,7 +34,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "utils/wait_event.h"
 
@@ -382,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..459f3d3cae4
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,260 @@
+Interrupts
+==========
+
+"Interrupts" are a set of flags that represent conditions that should
+be handled at a later time.  They are roughly analogous to Unix
+signals, except that they are handled cooperatively by checking for
+them at many points in the code. The CHECK_FOR_INTERRUPTS() macro is
+called at strategically located spots where it is normally safe to
+process interrupts.
+
+Well behaved backend code calls CHECK_FOR_INTERRUPTS() periodically in
+any long-running loops or other long computations, and never sleeps
+using mechanisms other than the WaitEventSet mechanism or the more
+convenient WaitInterrupt / WaitSockerOrInterrupt functions (except for
+bounded short periods, eg LWLock waits). Following these rules ensures
+that the backend doesn't become uncancellable for too long.
+
+Each interrupt type can have a handler function associated with it,
+which will be called by CHECK_FOR_INTERRUPTS() if the interrupt is
+pending.  A standard set of interrupt handlers are installed at
+process startup, but they can be overridden by
+SetInterruptHandler(). A handler can also be temporarily disabled by
+calling DisableInterrupt and re-enabled by EnableInterrupt.
+
+If an interrupt is raised that doesn't have a handler function or it
+is not enabled, the interrupt will remain pending until it is cleared,
+or the handler is re-enabled.
+
+If an interrupt is sent that was already pending for the target
+process, it is coalesced, ie. the target process will process it only
+once. Attempting to set an interrupt bit that is already set is very
+fast, amounting to merely an atomic op in shared memory.
+
+See src/include/ipc/interrupt.h for a list of all the interrupts used
+in core code. Extensions can define their own interrupts.
+
+HOLD/RESUME_INTERRUPTS
+----------------------
+
+Processing an interrupt may throw an error with ereport() (for
+example, query cancellation) or terminate the process gracefully. In
+some cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level
+subroutines that might sometimes be called in contexts that do *not*
+want to allow interrupt processing.  The HOLD_INTERRUPTS() and
+RESUME_INTERRUPTS() macros allow code to ensure that no interrupt
+handlers are called, even if CHECK_FOR_INTERRUPTS() gets called in a
+subroutine.  The interrupt will be held off until
+CHECK_FOR_INTERRUPTS() is done outside any HOLD_INTERRUPTS() ...
+RESUME_INTERRUPTS() section.
+
+HOLD_INTERRUPTS() blocks can be nested. If HOLD_INTERRUPTS() is called
+multiple times, interrupts are held until every HOLD_INTERRUPTS() call
+has been balanced with a RESUME_INTERRUPTS() call.
+
+Critical sections
+-----------------
+
+A related, but conceptually distinct, mechanism is the "critical
+section" mechanism.  A critical section not only holds off cancel/die
+interrupts, but causes any ereport(ERROR) or ereport(FATAL) to become
+ereport(PANIC) --- that is, a system-wide reset is forced.  Needless
+to say, only really *critical* code should be marked as a critical
+section! This mechanism is mostly used for XLOG-related code.
+
+Sending interrupts
+------------------
+
+Interrupt flags can be "raised" in different ways:
+- synchronously by code that wants to defer an action, by calling
+  RaiseInterrupt,
+- asynchronously by timers or other signal handlers, also by calling
+  RaiseInterrupt, or
+- "sent" by other backends with SendInterrupt
+
+Processing interrupts
+---------------------
+
+Interrupts are usually handled by handler functions, which are called
+from CHECK_FOR_INTERRUPTS() when the corresponding interrupt is
+pending. Use SetInterruptHandler() to install a handler function.
+
+Sometimes you don't want an interrupt to be processed at any random
+CHECK_FOR_INTERRUPTS() invocation. In that case, you can leave the
+interrupt handler disabled, and check for the interrupt explicitly at
+suitable spots with InterruptPending().
+
+INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
+interrupt needs to be serviced, without trying to do so immediately.
+Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
+which tells whether CHECK_FOR_INTERRUPTS() is sure to clear the
+interrupt.
+
+
+Waiting for an interrupt
+------------------------
+The correct pattern to wait for an event(s) that raises an interrupt
+is:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	/* Check if our interrupt was raised (clearing it if it was) */
+	if (ConsumeInterrupt(INTERRUPT_PRINTER_ON_FIRE))
+		break;
+
+	/*
+	 * Sleep until our interrupt or one of the standard interrupts is
+	 * received.
+	 */
+	WaitInterrupt(INTERRUPT_CFI_MASK |
+				  INTERRUPT_PRINTER_ON_FIRE,
+				  ...);
+}
+```
+
+Often an interrupt is used to wake up a process that is known to be
+waiting and not doing anything else. The INTERRUPT_WAIT_WAKEUP
+interrupt is reserved for such use cases where you don't need a
+dedicated interrupt flag. When using INTERRUPT_WAIT_WAKEUP, you need
+some other signaling, e.g. a flag in shared memory, to indicate that
+the desired event has happen, as you may sometimes receive false
+wakeups. The pattern using INTERRUPT_WAIT_WAKEUP looks like this:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+}
+```
+
+It's important to clear the interrupt *before* checking if there's
+work to do. Otherwise, if someone sets the interrupt between the check
+and the ClearInterrupt() call, you will miss it and Wait will
+incorrectly block. Another valid coding pattern looks like:
+
+```
+for (;;)
+{
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+}
+```
+
+This is useful to reduce interrupt traffic if it's expected that the
+loop's termination condition will often be satisfied in the first
+iteration; the cost is an extra loop iteration before blocking when it
+is not.  What must be avoided is placing any checks for asynchronous
+events after WaitInterrupt and before ClearInterrupt, as that creates
+a race condition.
+
+Custom interrupts in extensions
+-------------------------------
+
+An extension can allocate interrupt bits for its own purposes by
+calling RequestAddinInterrupt(). Note that interrupts are a somewhat
+scarce resource, so consider using INTERRUPT_WAIT_WAKEUP if all you
+need is a simple wakeup in some loop.
+
+
+Unix Signals
+============
+
+Postmaster and most child processes also respond to a few standard
+Unix signals:
+
+SIGHUP -> Raises INTERRUPT_CONFIG_RELOAD
+SIGINT -> Raises INTERRUPT_QUERY_CANCEL
+SIGTERM -> Raises INTERRUPT_TERMINATE
+SIGQUIT -> immediate shutdown, abort the process
+
+pg_ctl uses these Unix signals to tell postmaster to reload config,
+stop, etc. These are also mentioned in the user documentation.
+
+Postmaster cannot send interrupts to processes without PGPROC entries
+(just syslogger nowadays), and it doesn't know the PGPROC entries of
+other child processes anyway. Hence, it still uses the above Unix
+signals for postmaster -> child signaling. The only exception is when
+postmaster notifies a backend that a bgworker it launched has
+exited. Postmaster sends that interrupt directly. The backend
+registers explicitly for that notification, and supplies the
+ProcNumber to postmaster when registering.
+
+There are a few more exceptions:
+
+- A few processes like the checkpointer, archiver, and IO workers
+  don't react to SIGTERM, because Unix system shutdown sends a SIGTERM
+  to all processes, and we want them to exit only only after all other
+  process. Instead, the postmaster sends them SIGUSR2 after all the
+  other processes have exited.
+
+- Child processes use SIGUSR1 to request the postmaster to do various
+  actions or notify that some event has occurred. See pmsignal.c for
+  details on that mechanism
+
+
+TODO: Open questions
+====================
+
+TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
+----------------------------------------------------
+
+Postmaster cannot send interrupts to processes without PGPROC
+entries. Hence, it still uses plain Unix signals. Would be nice if
+postmaster could send interrupts directly. If we moved the interrupt
+mask to PMChildSlot, it could.  However, PGPROC seems like a more
+natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+d) Keep it as it is, continue to use signals for postmaster -> child
+   signaling
+
+
+TODO: Unique session id
+----------------------
+
+In some places, we read the pid of a process (from PGPROC or
+elsewhere), and send signal to it, accepting that the process may have
+already exited.  If we directly replace those uses of 'pid' with a
+ProcNumber, the ProcNumber might get reused much faster than the pid
+would. Solutions:
+
+a) Introduce the concept of a unique session ID that is never recycled
+   (or not for a long time, anyway). When reading the ProcNumber to
+   send an interrupt to, also read the session ID. When sending the
+   interrupt, check that the session ID matches.
+
+
+TODO: Other
+-----------
+
+- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
+  it's nevertheless more instructions than it used to be.
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
new file mode 100644
index 00000000000..597f0874e43
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,431 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint64 LocalPendingInterrupts;
+
+pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+static int nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
+
+/*
+ * Install an interrupt handler callback function for the given interrupt.
+ *
+ * You need to also enable the interrupt with EnableInterrupt(), unless you're
+ * replacing an existing handler function.
+ */
+void
+SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler)
+{
+	/*
+	 * XXX: It's somewhat inefficient to loop through all the bits, but this
+	 * isn't performance critical.
+	 */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+		{
+			/* Replace old handler */
+			interrupt_handlers[i] = handler;
+		}
+	}
+}
+
+/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
+void
+EnableInterrupt(InterruptMask interruptMask)
+{
+#ifdef USE_ASSERT_CHECKING
+	/* Check that the interrupt has a handler defined */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
+ *
+ * If an interrupt condition is pending, and it's safe to service it,
+ * then clear the flag and call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning, if it was set when entering.  (This is not the same as
+ * guaranteeing that it's still clear when we return; another interrupt could
+ * have arrived.  But we promise that any pre-existing one will have been
+ * serviced.)
+ */
+void
+ProcessInterrupts(void)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/* Check once what interrupts are pending */
+	interruptsToProcess =
+		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		{
+			/*
+			 * Clear the interrupt *before* calling the handler function, so
+			 * that if the interrupt is received again while the handler
+			 * function is being executed, we won't miss it.
+			 *
+			 * For similar reasons, we also clear the flags one by one even if
+			 * multiple interrupts are pending.  Otherwise if one of the
+			 * interrupt handlers bail out with an ERROR, we would have
+			 * already cleared the other bits, and would miss processing them.
+			 */
+			ClearInterrupt(UINT64_BIT(i));
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint64		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint64		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt 0x016%" PRIx64" to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in some aux processes that
+ * want to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
+
+/* Reserve an interrupt bit for use in an extension */
+InterruptMask
+RequestAddinInterrupt(void)
+{
+	InterruptMask result;
+
+	if (nextAddinInterruptBit == END_ADDIN_INTERRUPTS)
+		elog(ERROR, "out of addin interrupt bits");
+
+	result = UINT64_BIT(nextAddinInterruptBit);
+	nextAddinInterruptBit++;
+	return result;
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..589f755622a
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 *
+	 * The process may ignore the interrupts that these raise, e.g if query
+	 * cancellation is not applicable.  But there's no harm in having the
+	 * signal handlers in place anyway.
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is sent by postmaster to some aux processes, for different
+	 * purposes.  Such processes override this before unblocking signals, but
+	 * ignore it by default.
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/*
+	 * SIGALRM is used for timeouts, but the handler is established later in
+	 * InitializeTimeouts()
+	 */
+	pqsignal(SIGALRM, SIG_IGN);
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 795bfed8d19..314d34a9591 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index df7dc79b827..1a60ca8f78a 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,6 +15,7 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
@@ -421,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.
  *
@@ -457,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
@@ -496,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
@@ -678,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 4da6ac22ff9..3a0ff956823 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -533,8 +534,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 3f9257ab010..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,8 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -174,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -181,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -213,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -255,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -284,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -300,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -307,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -338,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -349,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -364,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 6570f27297b..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,8 +73,8 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
@@ -175,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);
@@ -287,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
@@ -306,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;
 }
@@ -1411,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;
 		}
 
@@ -2062,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 6e4bbfb5aa1..0a029eb02a8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -25,7 +26,6 @@
 
 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);
@@ -75,14 +75,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;
 }
 
@@ -169,27 +168,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
+			SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+						  pq_mq_parallel_leader_proc_number);
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 712a857cdb4..d20e6fba285 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0a730fb5b45..5763057a87a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -27,15 +27,17 @@
  * launcher has set up.
  *
  * If the fork() call fails in the postmaster, it sets a flag in the shared
- * memory area, and sends a signal to the launcher.  The launcher, upon
- * noticing the flag, can try starting the worker again by resending the
+ * memory area, sends a signal to the launcher, raising the
+ * INTERRUPT_AUTOVACUUM_WORKER_FINISHED interrupt. The launcher, upon
+ * noticing the interrupt, can try starting the worker again by resending the
  * signal.  Note that the failure can only be transient (fork failure due to
  * high load, memory pressure, too many processes, etc); more permanent
  * problems, like failure to connect to a database, are detected later in the
  * worker and dealt with just by having the worker exit normally.  The launcher
  * will launch a new worker again later, per schedule.
  *
- * When the worker is done vacuuming it sends SIGUSR2 to the launcher.  The
+ * When the worker is done vacuuming and exits, the postmaster sends SIGUSR2
+ * to the launcher, raising INTERRUPT_AUTOVACUUM_WORKER_FINISHED.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -151,9 +153,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;
@@ -287,6 +286,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
@@ -322,7 +324,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);
@@ -394,21 +396,23 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * Set up signal handlers.  We operate on databases much like a regular
-	 * backend, so we use the same signal handling.  See equivalent code in
-	 * tcop/postgres.c.
+	 * Set up signal and interrupt handlers.  We operate on databases much
+	 * like a regular backend, so we use mostly the same handling.  See
+	 * equivalent code in tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
+	SetStandardInterruptHandlers();
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Postmaster uses SIGUSR2 to raise INTERRUPT_AUTOVACUUM_WORKER_FINISHED */
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
+
+	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -455,7 +459,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();
@@ -497,7 +502,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();
 
 		/*
@@ -556,7 +561,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 */
 	}
@@ -571,41 +576,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -741,21 +749,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? */
@@ -773,17 +777,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();
 }
 
 /*
@@ -1350,8 +1343,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)
@@ -1363,8 +1356,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1399,22 +1391,18 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-
-	/*
-	 * 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, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	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
@@ -1545,12 +1533,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
 	{
@@ -2292,9 +2275,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);
 
 			/*
@@ -2452,7 +2434,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();
 		{
@@ -2541,9 +2523,8 @@ deleted:
 		 * Check for config changes before acquiring lock for further jobs.
 		 */
 		CHECK_FOR_INTERRUPTS();
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			VacuumUpdateCosts();
 		}
@@ -2665,7 +2646,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 8ea800c0bbd..ee16e9cb025 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 133af5aee97..20b7ba737ba 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -79,6 +79,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;
 
 /*
@@ -202,8 +204,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -244,6 +247,19 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	/*
+	 * FIXME: be extra paranoid and sanity check the proc number, since this
+	 * runs in the postmaster
+	 */
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -325,20 +341,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;
@@ -346,8 +362,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;
 		}
@@ -393,23 +409,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;
@@ -431,7 +430,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.
  */
@@ -476,8 +475,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);
 }
 
 /*
@@ -493,12 +492,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
@@ -511,27 +510,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;
+		}
 	}
 }
 
@@ -563,14 +569,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);
 		}
 	}
 }
@@ -624,11 +630,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;
 		}
 	}
 }
@@ -756,32 +757,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, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -987,15 +984,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
@@ -1111,6 +1099,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.
@@ -1211,8 +1218,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)
@@ -1232,9 +1240,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)
 		{
@@ -1242,7 +1250,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1256,8 +1264,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)
@@ -1275,9 +1284,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)
 		{
@@ -1285,7 +1294,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 dbd670c16cc..a53596d49ef 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * Set up interrupt handling
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -220,9 +216,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.
@@ -295,22 +291,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
@@ -325,10 +321,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 978a7d0e649..054872aabce 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -11,8 +11,10 @@
  * condition.)
  *
  * The normal termination sequence is that checkpointer is instructed to
- * execute the shutdown checkpoint by SIGINT.  After that checkpointer waits
- * to be terminated via SIGUSR2, which instructs the checkpointer to exit(0).
+ * execute the shutdown checkpoint by SIGINT, which raises the
+ * INTERRUPT_SHUTDOWN_XLOG interrupt.  After that checkpointer waits
+ * to be terminated via SIGUSR2, raising INTERRUP_TERMINATE, which instructs
+ * the checkpointer to exit(0).
  * All backends must be stopped before SIGINT or SIGUSR2 is issued!
  *
  * Emergency termination is by SIGQUIT; like any backend, the checkpointer
@@ -44,13 +46,14 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -85,10 +88,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
@@ -163,7 +167,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;
@@ -175,7 +178,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);
@@ -205,22 +208,28 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	CheckpointerShmem->checkpointer_pid = MyProcPid;
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * Note: we deliberately ignore SIGTERM, because during a standard Unix
-	 * system shutdown cycle, init will SIGTERM all processes at once.  We
-	 * want to wait for the backends to exit, whereupon the postmaster will
-	 * tell us it's okay to shut down (via SIGUSR2).
+	 * Ignore SIGTERM, because during a standard Unix system shutdown cycle,
+	 * init will SIGTERM all processes at once.  We want to wait for the
+	 * backends to exit, whereupon the postmaster will tell us it's okay to
+	 * shut down (via INTERRUPT_SHUTDOWN_XLOG).
+	 */
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessCheckpointerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
+	/* we will check for INTERRUPT_TERMINATE explicitly in the loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
@@ -359,15 +368,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;
 
 		/*
@@ -541,10 +550,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;
 		}
 
@@ -569,7 +578,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);
@@ -585,10 +594,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);
 	}
 
 	/*
@@ -597,7 +609,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -615,7 +627,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);
 	}
 
 	/*
@@ -625,55 +637,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();
 }
 
 /*
@@ -785,24 +780,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;
 
@@ -817,10 +806,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)
 	{
@@ -832,10 +823,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -924,12 +911,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);
 }
 
 
@@ -1102,14 +1088,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++)
@@ -1128,7 +1115,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1259,7 +1246,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;
@@ -1540,5 +1527,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * Simple signal handler for triggering a configuration reload.
- *
- * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending should be checked at a convenient place within the
- * main loop, or else the main loop should call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForShutdownRequest(SIGNAL_ARGS)
-{
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 10a5ef9e15c..1f4c3576e5b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -132,11 +132,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
  * ----------
@@ -148,10 +143,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -225,18 +221,27 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install an interrupt handler for it?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -247,8 +252,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;
 
@@ -282,13 +287,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);
 }
 
 
@@ -297,8 +301,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);
 }
 
 /*
@@ -309,7 +312,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
@@ -318,13 +321,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -334,7 +339,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);
 
@@ -346,6 +351,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -356,10 +362,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -408,7 +417,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;
 
 			/*
@@ -416,7 +425,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;
@@ -849,30 +858,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 d6133bfebc6..ea2bfcf217d 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -539,10 +539,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +557,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +577,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1644,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1681,20 @@ ServerLoop(void)
 								   0 /* postmaster posts no wait_events */ );
 
 		/*
-		 * Latch set by signal handler, or new connection pending on any of
-		 * our sockets? If the latter, fork a child process to deal with it.
+		 * Interrupt raised by signal handler, or new connection pending on
+		 * any of our sockets?  If the latter, fork a child process to deal
+		 * with it.
 		 */
 		for (int i = 0; i < nevents; i++)
 		{
-			if (events[i].events & WL_LATCH_SET)
-				ResetLatch(MyLatch);
+			if (events[i].events & WL_INTERRUPT)
+				ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/*
 			 * The following requests are handled unconditionally, even if we
-			 * didn't see WL_LATCH_SET.  This gives high priority to shutdown
-			 * and reload requests where the latch happens to appear later in
-			 * events[] or will be reported by a later call to
+			 * didn't see WL_INTERRUPT.  This gives high priority to shutdown
+			 * and reload requests where the interrupt event happens to appear
+			 * later in events[] or will be reported by a later call to
 			 * WaitEventSetWait().
 			 */
 			if (pending_pm_shutdown_request)
@@ -1985,7 +1986,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1996,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2073,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2234,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2259,6 +2260,7 @@ process_pm_child_exit(void)
 		 */
 		if (StartupPMChild && pid == StartupPMChild->pid)
 		{
+			elog(LOG, "STARTUP PROC EXITED");
 			ReleasePostmasterChildSlot(StartupPMChild);
 			StartupPMChild = NULL;
 
@@ -2581,6 +2583,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2631,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2659,14 @@ CleanupBackend(PMChild *bp,
 	}
 
 	/*
-	 * This backend may have been slated to receive SIGUSR1 when some
-	 * background worker started or stopped.  Cancel those notifications, as
-	 * we don't want to signal PIDs that are not PostgreSQL backends.  This
+	 * This backend may have been slated to receive INTERRUPT_WAIT_WAKEUP when
+	 * some background worker started or stopped.  Cancel those notifications,
+	 * as we don't want to signal PIDs that are not PostgreSQL backends.  This
 	 * gets skipped in the (probably very common) case where the backend has
 	 * never requested any such notifications.
 	 */
 	if (bp_bgworker_notify)
-		BackgroundWorkerStopNotifications(bp_pid);
+		BackgroundWorkerStopNotifications(bp_child_slot);
 
 	/*
 	 * If it was an autovacuum worker, wake up the launcher so that it can
@@ -3921,7 +3925,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4297,15 +4301,15 @@ maybe_start_bgworkers(void)
 		{
 			if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 			{
-				int			notify_pid;
+				ProcNumber	notify_proc_number;
 
-				notify_pid = rw->rw_worker.bgw_notify_pid;
+				notify_proc_number = GetNotifyProcNumberForRegisteredWorker(rw);
 
 				ForgetBackgroundWorker(rw);
 
 				/* Report worker is gone now. */
-				if (notify_pid != 0)
-					kill(notify_pid, SIGUSR1);
+				if (notify_proc_number != INVALID_PROC_NUMBER)
+					SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 330c10ddb0b..7a05a1ad333 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * Flags set by interrupt handlers for later service in the redo loop.
- */
-static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t shutdown_requested = false;
-static volatile sig_atomic_t promote_signaled = false;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -77,7 +73,7 @@ int			log_startup_progress_interval = 10000;	/* 10 sec */
 
 /* Signal handlers */
 static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
+static void StartupProcShutdownHandler(SIGNAL_ARGS);
 
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
@@ -92,16 +88,12 @@ static void StartupProcExit(int code, Datum arg);
 static void
 StartupProcTriggerHandler(SIGNAL_ARGS)
 {
-	promote_signaled = true;
-	WakeupRecovery();
-}
-
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-StartupProcSigHupHandler(SIGNAL_ARGS)
-{
-	got_SIGHUP = true;
-	WakeupRecovery();
+	/*
+	 * The INTERRUPT_CHECK_PROMOTE flag hints the startup process to check
+	 * for the signal file, while INTERRUPT_WAL_ARRIVED wakes up the
+	 * process from sleep.
+	 */
+	RaiseInterrupt(INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -111,8 +103,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +122,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +150,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +169,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -225,15 +202,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -241,6 +216,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
 	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
 
+	/* Only set up a subset of the standard interrupt handlers */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessStartupProcTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessStartupProcConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
 	/*
 	 * Unblock signals (they were blocked when the postmaster forked us)
 	 */
@@ -268,7 +250,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -278,18 +260,6 @@ PostRestoreCommand(void)
 	in_restore_command = false;
 }
 
-bool
-IsPromoteSignaled(void)
-{
-	return promote_signaled;
-}
-
-void
-ResetPromoteSignaled(void)
-{
-	promote_signaled = false;
-}
-
 /*
  * Set a flag indicating that it's time to log a progress report.
  */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 8515bfb9653..c9faa76b6a9 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -272,16 +272,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -322,7 +320,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
@@ -332,9 +330,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 */
@@ -350,14 +351,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);
 
 			/*
@@ -1182,7 +1182,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);
 	}
@@ -1193,8 +1193,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();
@@ -1585,9 +1585,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 4e4eed4d076..d8c754ad280 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -31,17 +31,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -146,7 +146,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);
@@ -240,16 +240,15 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 			(errmsg_internal("WAL summarizer started")));
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -309,10 +308,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) */
@@ -349,7 +346,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();
@@ -624,8 +621,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)
@@ -640,7 +637,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -849,27 +846,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();
 }
 
 /*
@@ -1008,7 +995,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);
@@ -1495,7 +1482,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1533,7 +1520,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. */
@@ -1633,11 +1620,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1684,7 +1675,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,
@@ -1703,7 +1694,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 1851417ad1b..93dfc98cc34 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,14 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -209,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))
 		{
@@ -222,11 +221,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -250,9 +248,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 1730ace5490..1544d0cd76e 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,10 +157,12 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
@@ -238,12 +240,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -706,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)
@@ -759,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);
@@ -805,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);
 			}
 		}
 		else
@@ -844,9 +831,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -871,15 +857,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();
 
 	/*
@@ -940,8 +929,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;
@@ -975,29 +963,7 @@ ParallelApplyWorkerMain(Datum main_arg)
 	set_apply_error_context_origin(originname);
 
 	LogicalParallelApplyLoop(mqh);
-
-	/*
-	 * The parallel apply worker must not get here because the parallel apply
-	 * worker will only stop when it receives a SIGTERM or SIGUSR2 from the
-	 * leader, or SIGINT from itself, or when there is an error. None of these
-	 * cases will allow the code to reach here.
-	 */
-	Assert(false);
-}
-
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	Assert(false); /* LogicalParallelApplyLoop never returns */
 }
 
 /*
@@ -1064,7 +1030,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1074,6 +1040,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1097,8 +1066,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1189,14 +1156,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();
 		}
 
@@ -1261,13 +1229,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 e6112e11ec2..d66ba0b6f5a 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -58,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;
@@ -183,7 +184,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -219,27 +219,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -473,6 +474,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
@@ -539,7 +541,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))
@@ -566,7 +567,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -589,13 +590,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -616,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -630,13 +632,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -663,7 +666,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);
@@ -672,8 +675,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -710,13 +714,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
@@ -738,7 +742,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.
  */
@@ -747,7 +751,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -815,7 +819,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);
@@ -847,6 +851,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;
 }
 
@@ -858,7 +863,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;
 }
 
 /*
@@ -1019,7 +1024,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);
@@ -1045,6 +1049,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1193,8 +1198,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);
 }
 
 /*
@@ -1208,13 +1217,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).
@@ -1256,6 +1268,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)
 		{
@@ -1416,21 +1429,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);
 		}
 	}
 
@@ -1585,7 +1596,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 165f909b3ba..92c903d7f96 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 5e1ee7e4728..9dbf89cb832 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,9 +59,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -79,9 +80,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -104,7 +105,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1223,7 +1224,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;
@@ -1286,13 +1287,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1314,7 +1316,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1358,7 +1362,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
@@ -1404,13 +1408,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1418,7 +1425,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);
 
@@ -1430,16 +1437,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);
 
@@ -1454,7 +1461,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;
@@ -1529,19 +1536,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1681,7 +1684,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);
 
@@ -1720,7 +1723,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1737,7 +1740,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1745,8 +1748,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1754,13 +1757,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1845,7 +1849,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1923,7 +1927,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 19a3c21a863..1cef9a499e6 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -167,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;
@@ -218,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;
@@ -518,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.
 						 */
@@ -697,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 75af3a71ca8..29ad2ac4f92 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -4053,11 +4054,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();
@@ -4176,11 +4174,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4201,23 +4199,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5888,8 +5885,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 28c7019402b..9742ddd1fd4 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -622,7 +622,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	int			active_pid;
 
 	Assert(name != NULL);
 
@@ -685,7 +684,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -707,7 +705,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -1968,7 +1966,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1978,7 +1976,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		int			active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2062,11 +2059,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2105,22 +2097,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2161,7 +2156,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3253,11 +3249,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))
@@ -3267,6 +3260,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a way to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 7ea6001e9ad..35ec8047391 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -265,22 +266,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -294,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))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -314,9 +315,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.")));
@@ -325,11 +325,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,
@@ -337,7 +341,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -944,7 +948,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 352a3f31e95..f0698e61194 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -246,16 +247,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
-	/* Properly accept or ignore signals the postmaster might send us */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -439,9 +432,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();
@@ -514,25 +506,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -680,7 +674,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -712,8 +706,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)
@@ -1384,7 +1382,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 42e3e170bc0..56ca8f7389c 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"
@@ -332,7 +333,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 5acc8ee3ac8..446eafe694f 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -201,15 +202,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_STOPPING = false;
-
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM.
+ * When set, the main loop is responsible for checking
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -282,7 +280,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -370,7 +368,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -738,8 +737,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();
@@ -773,7 +780,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)
@@ -972,12 +981,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1036,9 +1047,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1475,7 +1486,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1529,7 +1540,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1619,12 +1630,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();
 
@@ -1664,23 +1671,27 @@ ProcessPendingWrites(void)
 
 		/* Sleep until something happens or we time out */
 		WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime,
-				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
+				   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+				   CheckForInterruptsMask |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1783,7 +1794,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1870,12 +1881,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();
@@ -1885,7 +1896,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			XLogBackgroundFlush();
 
 		/*
@@ -1910,7 +1921,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1992,11 +2003,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2022,7 +2037,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2895,6 +2910,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2909,13 +2925,20 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
 
-		CHECK_FOR_INTERRUPTS();
+		/* Clear any already-pending wakeups */
+		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();
@@ -2964,13 +2987,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);
 		}
 
@@ -3022,7 +3045,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3060,6 +3088,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3116,6 +3145,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3210,7 +3240,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3579,8 +3609,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3697,52 +3727,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3825,24 +3846,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SetLatch(), helping
-	 * walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3890,16 +3915,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 d2c9cd6f20a..64df384656b 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d9617c20e76..80eadefcd1d 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,22 +262,24 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
-			pgaio_debug_io(DEBUG4, staged_ios[i],
+			pgaio_debug_io(LOG, staged_ios[i],
 						   "choosing worker %d",
 						   worker);
 		}
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -392,20 +393,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -453,11 +454,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -490,13 +491,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -567,18 +568,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d1babaff023..cb61e6e1fce 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3345,7 +3346,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);
@@ -3525,7 +3526,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3606,7 +3607,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3697,7 +3698,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);
 	}
@@ -6643,12 +6644,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other signals as well. We
+		 * take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e208457df27..367c1168216 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -357,8 +357,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	/*
 	 * Block all blockable signals, except SIGQUIT.  posix_fallocate() can run
 	 * for quite a long time, and is an all-or-nothing operation.  If we
-	 * allowed SIGUSR1 to interrupt us repeatedly (for example, due to
-	 * recovery conflicts), the retry loop might never succeed.
+	 * allowed signals to interrupt us repeatedly (for example, due to config
+	 * file reloads), the retry loop might never succeed.
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..806947571ae 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -172,13 +173,12 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests; we're doing our best to
-	 * close up shop already.  Note that the signal handlers will not set
-	 * these flags again, now that proc_exit_inprogress is set.
+	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
+	 * to prevent any more interrupts from being processed; we're doing our
+	 * best to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 40312df2cac..b34e2cb731e 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"
@@ -3450,27 +3451,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3510,10 +3511,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;
 		}
@@ -3545,21 +3546,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 5d33559926a..b80426828b1 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -26,7 +27,7 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -34,28 +35,21 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
  * (or greater) appears in the pss_barrierGeneration flag of every process,
  * we know that the message has been received everywhere.
@@ -65,7 +59,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) */
@@ -105,7 +98,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -150,7 +142,6 @@ ProcSignalShmemInit(void)
 			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);
@@ -181,9 +172,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -232,9 +220,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;
@@ -269,73 +258,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
@@ -380,8 +302,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
@@ -392,25 +314,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;
@@ -470,23 +379,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.
  *
@@ -502,12 +394,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
@@ -639,68 +527,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -759,6 +586,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 3ce6068ac54..ab995093140 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * process interrupts are used for synchronization.  Only the sender may send,
  * and only the receiver may receive.  This is intended to allow a user
  * backend to communicate with worker backends that it has registered.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -44,10 +45,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.
@@ -112,7 +114,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
@@ -214,7 +216,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));
 }
 
 /*
@@ -232,7 +234,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));
 }
 
 /*
@@ -341,16 +343,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
@@ -539,7 +540,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;
 	}
 
@@ -557,16 +558,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)
@@ -619,8 +619,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)
 	{
@@ -895,7 +895,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +993,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
@@ -1001,7 +1001,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;
@@ -1009,17 +1009,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();
@@ -1054,7 +1055,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.
 			 */
@@ -1150,25 +1151,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);
 	}
 }
 
@@ -1250,14 +1255,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;
@@ -1293,7 +1302,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 d48b4fe3799..aaeebca6c91 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -41,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
@@ -201,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..dcabd25956a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and will set the
- * processes latch. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * setting the process's latch.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,15 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
-		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
-		 * can just call AcceptInvalidationMessages() to do this.  If we
-		 * aren't, we start and immediately end a transaction; the call to
-		 * AcceptInvalidationMessages() happens down inside transaction start.
+		 * run, which will do the necessary work.  If we are inside a
+		 * transaction we can just call AcceptInvalidationMessages() to do
+		 * this.  If we aren't, we start and immediately end a transaction;
+		 * the call to AcceptInvalidationMessages() happens down inside
+		 * transaction start.
 		 *
 		 * It is awfully tempting to just call AcceptInvalidationMessages()
 		 * without the rest of the xact start/stop overhead, and I think that
@@ -190,14 +148,20 @@ ProcessCatchupInterrupt(void)
 		 */
 		if (IsTransactionOrTransactionBlock())
 		{
-			elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt inside transaction");
 			AcceptInvalidationMessages();
 		}
 		else
 		{
-			elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt outside transaction");
 			StartTransactionCommand();
 			CommitTransactionCommand();
 		}
+
+		/*
+		 * If another catchup interrupt arrived while we were procesing the
+		 * previous one, process the new one too.
+		 */
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -658,8 +666,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -670,8 +678,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index d83afbfb9d6..c9242d6fb89 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -596,7 +597,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
@@ -698,7 +699,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
@@ -746,7 +751,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:
@@ -766,15 +775,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
@@ -838,9 +848,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);
@@ -936,6 +956,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -945,6 +966,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -954,6 +976,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 772e350a0c0..612de8b35e9 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -128,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,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -183,9 +184,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
 
@@ -235,7 +243,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)
@@ -285,12 +293,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");
@@ -311,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -349,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -412,7 +426,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;
 
@@ -496,9 +511,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)
 		{
@@ -535,7 +550,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
@@ -553,10 +568,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.
@@ -566,7 +577,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, InterruptMask interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +591,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 */
@@ -608,10 +616,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)
@@ -645,14 +653,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)
@@ -682,40 +690,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +747,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)
@@ -794,9 +795,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)
@@ -858,9 +858,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;
@@ -886,8 +886,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 |
@@ -902,10 +901,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
 	{
@@ -985,10 +984,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)
 	{
@@ -1044,6 +1046,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);
 
@@ -1065,100 +1068,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		InterruptMask old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
+		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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1229,16 +1249,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1391,13 +1411,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1513,16 +1533,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1621,6 +1641,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
@@ -1726,19 +1755,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1783,7 +1808,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
@@ -1889,18 +1914,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)
 {
@@ -1917,7 +1941,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;
@@ -2004,7 +2028,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2014,12 +2037,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();
@@ -2027,12 +2049,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..f961054a4ba 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1584,7 +1585,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;
@@ -3617,7 +3622,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 ba7eaed1a13..44809fea210 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -214,6 +215,8 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -301,14 +304,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -518,13 +535,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);
@@ -688,13 +700,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -717,6 +724,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.
@@ -986,21 +997,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1059,7 +1069,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 */
@@ -1078,11 +1088,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);
 
@@ -1410,18 +1429,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
 	{
@@ -1467,9 +1486,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)
 			{
@@ -1675,8 +1695,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)
 	{
@@ -1713,7 +1733,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 links invalid.
  *
@@ -1742,7 +1762,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1899,14 +1919,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;
 }
 
@@ -1985,34 +2003,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 378c2a03f39..dd8b1fc10f5 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 b1accc68b95..fbed7d82e98 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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 c517115927c..9595476c8e0 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -198,6 +198,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
 
+	/*
+	 * FIXME: install INTERRUPT_TERMINATE handler at least? Not really needed
+	 * because no one else can send us interrupts yet, and we don't raise them
+	 * within the backend yet either. But if we changed postmaster to send
+	 * INTERRUPT_TERMINATE directly, for example, we'd need to handle it
+	 * already.
+	 */
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b88ce440b1d..bb022270c75 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -335,8 +336,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -353,11 +352,23 @@ SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
+
+	/*
+	 * Don't allow query cancel interrupts while reading input from the
+	 * client, because we might lose sync in the FE/BE protocol.  (Die
+	 * interrupts are OK, because we won't read any further messages from the
+	 * client in that case.)
+	 *
+	 * See similar logic in ProcessRecoveryConflictInterrupt().
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Get message type code from the frontend.
 	 */
-	HOLD_CANCEL_INTERRUPTS();
 	pq_startmsgread();
 	qtype = pq_getbyte();
 
@@ -464,7 +475,9 @@ SocketBackend(StringInfo inBuf)
 	 */
 	if (pq_getmessage(inBuf, maxmsglen))
 		return EOF;				/* suitable message already logged */
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	return qtype;
 }
@@ -488,104 +501,6 @@ ReadCommand(StringInfo inBuf)
 	return result;
 }
 
-/*
- * ProcessClientReadInterrupt() - Process interrupts specific to client reads
- *
- * This is called just before and after low-level reads.
- * 'blocked' is true if no data was available to read and we plan to retry,
- * false if about to read or done reading.
- *
- * Must preserve errno!
- */
-void
-ProcessClientReadInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (DoingCommandRead)
-	{
-		/* Check for general interrupts that arrived before/while reading */
-		CHECK_FOR_INTERRUPTS();
-
-		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
-			ProcessCatchupInterrupt();
-
-		/* Process notify interrupts, if any */
-		if (notifyInterruptPending)
-			ProcessNotifyInterrupt(true);
-	}
-	else if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If there is no data available to read, then it's safe
-		 * (and sane) to handle that now.  If we haven't tried to read yet,
-		 * make sure the process latch is set, so that if there is no data
-		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while reading.
-		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
-/*
- * ProcessClientWriteInterrupt() - Process interrupts specific to client writes
- *
- * This is called just before and after low-level writes.
- * 'blocked' is true if no data could be written and we plan to retry,
- * false if about to write or done writing.
- *
- * Must preserve errno!
- */
-void
-ProcessClientWriteInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If it's not possible to write, then we should handle
-		 * that immediately, else a stuck client could indefinitely delay our
-		 * response to the signal.  If we haven't tried to write yet, make
-		 * sure the process latch is set, so that if the write would block
-		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while writing.
-		 */
-		if (blocked)
-		{
-			/*
-			 * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't
-			 * service ProcDiePending.
-			 */
-			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-			{
-				/*
-				 * We don't want to send the client the error message, as a)
-				 * that would possibly block again, and b) it would likely
-				 * lead to loss of protocol sync because we may have already
-				 * sent a partial protocol message.
-				 */
-				if (whereToSendOutput == DestRemote)
-					whereToSendOutput = DestNone;
-
-				CHECK_FOR_INTERRUPTS();
-			}
-		}
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
 /*
  * Do raw parsing (only).
  *
@@ -2910,7 +2825,7 @@ drop_unnamed_stmt(void)
  * Either some backend has bought the farm, or we've been told to shut down
  * "immediately"; so we need to stop what we're doing and exit.
  */
-void
+static void
 quickdie(SIGNAL_ARGS)
 {
 	sigaddset(&BlockSig, SIGQUIT);	/* prevent nested calls */
@@ -3007,50 +2922,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-void
-StatementCancelHandler(SIGNAL_ARGS)
-{
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3067,22 +2958,91 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
- * in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	/*
+	 * Check the conflicts one by one, clearing each flag only before
+	 * processing the particular conflict.  This ensures that if multiple
+	 * conflicts are pending, we come back here to process the remaining
+	 * conflicts, if an error is thrown during processing one of them.
+	 */
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
+		 reason++)
+	{
+		if ((pending & (1 << reason)) != 0)
+		{
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
+			ProcessRecoveryConflict(reason);
+		}
+	}
+}
+
+void
+ProcessRecoveryConflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
@@ -3242,14 +3202,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,72 +3240,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
-
-	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
-	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * Check the conflicts one by one, clearing each flag only before
-	 * processing the particular conflict.  This ensures that if multiple
-	 * conflicts are pending, we come back here to process the remaining
-	 * conflicts, if an error is thrown during processing one of them.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
- *
- * If an interrupt condition is pending, and it's safe to service it,
- * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
- *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts
- * is guaranteed to clear the InterruptPending flag before returning.
- * (This is not the same as guaranteeing that it's still clear when we
- * return; another interrupt could have arrived.  But we promise that
- * any pre-existing one will have been serviced.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3394,65 +3313,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		CheckClientConnectionPending = false;
-
-		/*
-		 * Check for lost connection and re-arm, if still configured, but not
-		 * if we've arrived back at DoingCommandRead state.  We don't want to
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * Check for lost connection and re-arm, if still configured, but not if
+	 * we've arrived back at DoingCommandRead state.  We don't want to wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
+
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
 
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
+
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
-	{
-		/*
-		 * Re-arm InterruptPending so that we process the cancel request as
-		 * soon as we're done reading the message.  (XXX this is seriously
-		 * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we
-		 * can't use that macro directly as the initial test in this function,
-		 * meaning that this code also creates opportunities for other bugs to
-		 * appear.)
-		 */
-		InterruptPending = true;
-	}
-	else if (QueryCancelPending)
+
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3505,76 +3414,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * interrupt.  We need to unset the flag before the injection point,
-		 * otherwise we could loop in interrupts checking.
-		 */
-		IdleInTransactionSessionTimeoutPending = false;
-		if (IdleInTransactionSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4238,9 +4137,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4253,13 +4156,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4273,7 +4188,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4282,11 +4204,31 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4454,11 +4396,14 @@ PostgresMain(const char *dbname, const char *username)
 		 * forgetting a timeout cancel.
 		 */
 		disable_all_timeouts(false);	/* do first to avoid race condition */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 		idle_in_transaction_timeout_enabled = false;
 		idle_session_timeout_enabled = false;
 
 		/* Not reading from the client anymore. */
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 		DoingCommandRead = false;
 
 		/* Make sure libpq is in a good state */
@@ -4639,7 +4584,7 @@ PostgresMain(const char *dbname, const char *username)
 				 * were received during the just-finished transaction, they'll
 				 * be seen by the client before ReadyForQuery is.
 				 */
-				if (notifyInterruptPending)
+				if (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4728,6 +4673,28 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		DoingCommandRead = true;
 
+		/*
+		 * Let cache inval catchup requests to be processed while we're idle.
+		 */
+		EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+
+		/*
+		 * When we're truly idle, i.e. no transaction in progress, we can
+		 * additionally process any async LISTEN/NOTIFY messages and stats
+		 * update requests.
+		 */
+		if (!IsTransactionOrTransactionBlock())
+		{
+			EnableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+			EnableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+		}
+
+		/*
+		 * If any of the newly-enabled interrupts are already pending, process
+		 * them now.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * (3) read a command (loop blocks here)
 		 */
@@ -4757,22 +4724,22 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 87070235d11..6aa69805891 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -295,14 +295,7 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 	}
 
-	procNumber = GetNumberFromPGProc(proc);
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
+	SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc));
 
 	PG_RETURN_BOOL(true);
 }
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 32a787d7df7..9f660363921 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,16 +346,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;
 
@@ -374,11 +374,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 8deb2369471..df7657031b2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1739,10 +1739,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 59315e94e3e..1a0d8008c70 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -527,7 +527,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,6 @@
 
 ProtocolVersion FrontendProtocol;
 
-volatile sig_atomic_t InterruptPending = false;
-volatile sig_atomic_t QueryCancelPending = false;
-volatile sig_atomic_t ProcDiePending = false;
-volatile sig_atomic_t CheckClientConnectionPending = false;
-volatile sig_atomic_t ClientConnectionLost = false;
-volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t TransactionTimeoutPending = false;
-volatile sig_atomic_t IdleSessionTimeoutPending = false;
-volatile sig_atomic_t ProcSignalBarrierPending = false;
-volatile sig_atomic_t LogMemoryContextPending = false;
-volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 QueryCancelHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
 
 int			MyProcPid;
 pg_time_t	MyStartTime;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fadf856d9bc..fdcee946405 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -143,25 +139,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -201,10 +184,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.
@@ -225,48 +207,6 @@ InitStandaloneProcess(const char *argv0)
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
 
-void
-SwitchToSharedLatch(void)
-{
-	Assert(MyLatch == &LocalLatchData);
-	Assert(MyProc != NULL);
-
-	MyLatch = &MyProc->procLatch;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	/*
-	 * Set the shared latch as the local one might have been set. This
-	 * shouldn't normally be necessary as code is supposed to check the
-	 * condition before waiting for the latch, but a bit care can't hurt.
-	 */
-	SetLatch(MyLatch);
-}
-
-void
-InitProcessLocalLatch(void)
-{
-	MyLatch = &LocalLatchData;
-	InitLatch(MyLatch);
-}
-
-void
-SwitchBackToLocalLatch(void)
-{
-	Assert(MyLatch != &LocalLatchData);
-	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);
-
-	MyLatch = &LocalLatchData;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	SetLatch(MyLatch);
-}
-
 /*
  * Return a human-readable string representation of a BackendType.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..4f1aa94a661 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -1371,20 +1372,19 @@ ShutdownPostgres(int code, Datum arg)
 static void
 StatementTimeoutHandler(void)
 {
-	int			sig = SIGINT;
-
 	/*
 	 * During authentication the timeout is used to deal with
 	 * authentication_timeout - we want to quit in response to such timeouts.
 	 */
 	if (ClientAuthInProgress)
-		sig = SIGTERM;
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+	else
+		RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, sig);
-#endif
-	kill(MyProcPid, sig);
+	/*
+	 * FIXME: we used to signal the whole process group. Is that important, if
+	 * we're e.g. executing archive_command ?
+	 */
 }
 
 /*
@@ -1393,51 +1393,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
- * to call this function if we see LogMemoryContextPending set.
- * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
- * the target process for logging of memory contexts is a backend.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/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 01bdf2bec1f..21b0b0a1ec7 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -53,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;
 
@@ -70,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..239d58f7597
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,370 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ */
+#include "storage/waiteventset.h"
+
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 63
+
+/*
+ * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+ * waiting for an interrupt.  If set, the backend needs to be woken up when a
+ * bit in the pending interrupts mask is set.  It's used internally by the
+ * interrupt machinery, and cannot be used directly in the public functions.
+ * It's named differently to distinguish it from the actual interrupt flags.
+ */
+#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
+
+extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 412bc9758fb..35fbff9a763 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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 c62fffb5998..3ba2b82b3a8 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -17,7 +17,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/meson.build b/src/include/meson.build
index b940c5cd3d6..d0cfcaec84b 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -135,6 +135,7 @@ header_subdirs = [
   'executor',
   'fe_utils',
   'foreign',
+  'ipc',
   'jit',
   'lib',
   'libpq',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..476e60a4d06 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,132 +28,15 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
-
-#define InvalidPid				(-1)
-
-
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/interrupt.h"
 #endif
 
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
+#define InvalidPid				(-1)
 
 
 /*****************************************************************************
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
-
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TableSyncWorkerMain(Datum main_arg);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if
+								 * not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index c1285fdd1bc..4b6449e974a 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index fbdadc86959..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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 */
-
-/*
- * 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 1f1ab7d02ca..00ea9f86422 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -18,7 +18,6 @@
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -190,9 +189,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -236,16 +232,6 @@ struct PGPROC
 
 	BackendType backendType;	/* what kind of process is this? */
 
-	/*
-	 * While in hot standby mode, shows that a conflict signal has been sent
-	 * for the current transaction. Set/cleared while holding ProcArrayLock,
-	 * though not required. Accessed without lock, if needed.
-	 *
-	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
-	 * enum value.
-	 */
-	pg_atomic_uint32 pendingRecoveryConflicts;
-
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
@@ -337,6 +323,24 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint64 pendingInterrupts;
+
+	/*
+	 * While in hot standby mode, shows that a conflict signal has been sent
+	 * for the current transaction. Set/cleared while holding ProcArrayLock,
+	 * though not required. Accessed without lock, if needed.
+	 *
+	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
+	 * enum value.
+	 */
+	pg_atomic_uint32 pendingRecoveryConflicts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -451,6 +455,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;
@@ -533,9 +539,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..a7f974c5ba2 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,17 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +73,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
+
+struct ResourceOwnerData;
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  struct Latch *latch, void *user_data);
+							  uint64 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint64 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 11ab1717a16..5d47643eb77 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 4c31e503eb1..0bd50bdb23f 100644
--- a/src/port/pgsleep.c
+++ b/src/port/pgsleep.c
@@ -32,10 +32,10 @@
  *
  * CAUTION: It's not a good idea to use long sleeps in the backend.  They will
  * silently return early if a signal is caught, but that doesn't include
- * latches being set on most OSes, and even signal handlers that set MyLatch
- * might happen to run before the sleep begins, allowing the full delay.
- * Better practice is to use WaitLatch() with a timeout, so that backends
- * respond to latches and signals promptly.
+ * interrupts being received on most OSes, and even signal handlers that raise
+ * an interrupt might happen to run before the sleep begins, allowing the full
+ * delay.  Better practice is to use WaitInterrupt() with a timeout, so that
+ * backends respond to interrupts and signals promptly.
  */
 void
 pg_usleep(long microsec)
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index a0aec04d994..777d00283da 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 579e5933d28..e6169f97a9e 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -133,6 +134,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;
@@ -222,8 +224,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)
@@ -264,6 +264,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;
@@ -286,11 +289,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 fe1794c6077..d8675ac1423 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "varatt.h"
 
@@ -170,6 +170,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
@@ -234,13 +236,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 368e4f3f234..c75b5edaafa 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,7 +19,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -52,15 +52,12 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
-	 * Establish signal handlers.
-	 *
-	 * We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
-	 * it would a normal user backend.  To make that happen, we use die().
+	 * The standard interrupt and signal handlers are OK for us, in particular
+	 * we want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
+	 * it would a normal user backend. Just unblock signals.
 	 */
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
@@ -122,13 +119,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 d1e4a2bd952..8f9fa9e1834 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -155,11 +153,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -213,26 +210,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
@@ -366,7 +362,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.
@@ -420,8 +415,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6e2d876a40f..eb3bdfc33e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1564,7 +1565,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2028,6 +2028,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2026-02-14 21:56             ` Andres Freund <[email protected]>
  2026-02-18 00:11               ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Andres Freund @ 2026-02-14 21:56 UTC (permalink / raw)
  To: Heikki Linnakangas <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Hi,

On 2026-02-13 19:11:52 +0200, Heikki Linnakangas wrote:
> On 29/01/2026 03:05, Heikki Linnakangas wrote:
> > Here's a new rebased and massively re-worked patch.
> >
> > The patches are split differently than in the previous version:
> > - Patches 0001-0005 are just refactoring around recovery conflicts which
> > I posted on a separate thread [1]. Please review and comment there.
> > - Patches 0006 and 0007 are small cleanups that could be applied early
> > (after updating the docs, as noted in TODO comment there).
> > - All the interesting bits for this thread are in the last, massive patch.
> >
> > To review this, I suggest starting from the new src/backend/ipc/
> > README.md file. It gives a good overview of the mechanism (or if it
> > doesn't, that's valuable feedback :-) ). It also contains a bunch of
> > Open Questions at the bottom; I'd love to hear opinions and ideas on
> > those.
>
> New version attached. Lots of little cleanups, and a few more notable
> changes:
>
> - Switched to 64-bit interrupt masks. This gives more headroom for
> extensions to reserve their own custom interrupts

I don't think as done here that's quite legal - we can't rely on 64bit atomics
inside signal handlers, because they may be emulated with a spinlock. Which in
turn means that interrupting oneself while modifying a 64bit atomic could
result in a self-deadlock.

It may be possible to make it safe to do the 64bit atomics emulation signal
safe, but I think the complexity and the overhead would likely make that
problematic.

I guess I'd just emulate it by splitting the interrupt mask into two? Perhaps
with a special interrupt bit set in the first atomic indicating that the
second word has pending interrupts?


If we force everyone to touch all the WaitLatch() calls, how about we move to
a saner unit for timeout? For one, long is stupid, due to the platform
dependent length. For another, a millisecond is too long a sleep time for some
things.

I wonder if we really need timeout handlers in the current form anymore. What
timeouts now do is to just raise an interrupt that is then reacted to
subsequently. Seems we should just change RegisterTimeout to accept the
interrupt it should trigger?



> - I mostly gave up on sending interrupts from postmaster to child processes.
> So postmaster still uses kill() to tell child processes to exit or do other
> things. There's a TODO section in the README about that, but this is fine
> for now.
>
> - Implemented a RequestAddinInterrupt() function for extensions to request
> interrupt bits. (TODO: use it in one of the example extensions, to show how
> to use it and to test that it works)



> From 3a81c457dc2b7daf89b8cdf4f058c1aaa52342da Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Fri, 13 Feb 2026 13:19:47 +0200
> Subject: [PATCH 1/5] Ignore SIGINT in walwriter and walsummarizer
>
> Previously, SIGINT was treated the same as SIGTERM in walwriter and
> walsummarizer. That decision goes back to when the walwriter process
> was introduced (commit ad4295728e04), and was later copied to
> walsummarizer. It was a pretty arbitrary decision back then, and we
> haven't adopted that convention in all the other processes that have
> been introduced later.
>
> Summary of how other processes respond to SIGINT:
> - Autovacuum launcher: Cancel the current iteration of launching
> - bgworker: Ignore (unless connected to a database)
> - checkpointer: Request shutdown checkpoint
> - pgarch: Ignore
> - startup process: Ignore
> - walreceiver: Ignore
> - IO worker: die()

- bgwriter: Ignore



> IO workers are a notable exception in that they exit on SIGINT, and
> there's a documented reason for that: IO workers ignore SIGTERM, so
> SIGINT provides a way to manually kill them. (They do respond to
> SIGUSR2, though, like all the other processes that we don't want to
> exit immediately on SIGTERM on operating system shutdown.)
>
> To make this a little more consistent, ignore SIGINT in walwriter and
> walsummarizer. They have no "query" to cancel, and they react to
> SIGTERM just fine.

WFM.  Let's get this merged.


> diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
> index 21de158adbb..b88ce440b1d 100644
> --- a/src/backend/tcop/postgres.c
> +++ b/src/backend/tcop/postgres.c
> @@ -4285,13 +4285,6 @@ PostgresMain(const char *dbname, const char *username)
>  		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
>  		pqsignal(SIGUSR2, SIG_IGN);
>  		pqsignal(SIGFPE, FloatExceptionHandler);
> -
> -		/*
> -		 * Reset some signals that are accepted by postmaster but not by
> -		 * backend
> -		 */
> -		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
> -									 * platforms */
>  	}
>
>  	/* Early initialization */

For a moment I wondered if there's a problem for single user mode, which also
goes through this path: But I think not, because that will never have set the
SIGCHLD handler to a non-default value, and the default is ignore...


> From 63d1a57f4906a924c426def4e1a7f27a71611b28 Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Tue, 20 Jan 2026 16:57:25 +0200
> Subject: [PATCH 3/5] Use standard die() handler for SIGTERM in bgworkers
> -/*
> - * Standard SIGTERM handler for background workers
> - */
> -static void
> -bgworker_die(SIGNAL_ARGS)
> -{
> -	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
> -
> -	ereport(FATAL,
> -			(errcode(ERRCODE_ADMIN_SHUTDOWN),
> -			 errmsg("terminating background worker \"%s\" due to administrator command",
> -					MyBgworkerEntry->bgw_type)));
> -}
> -
>  /*
>   * Main entry point for background worker processes.
>   */

Uh, huh. So we were defaulting to completely unsafe code in bgworkers all this
time?  This obviously can self-deadlock against memory allocations etc in the
interrupted code... Or cause confusion with the IO streams for stderr. Or ...

We really need some instrumentation that fails if we do allocations in signal
handlers etc.



> From c18137939a0d2561fedc247a3f3135355923ffa8 Mon Sep 17 00:00:00 2001
> From: Heikki Linnakangas <[email protected]>
> Date: Fri, 13 Feb 2026 14:23:35 +0200
> Subject: [PATCH 4/5] Refactor how some aux processes advertise their
>  ProcNumber
>
> This moves the responsibility of setting the
> ProcGlobal->walrewriterProc and checkpointerProc fields to
> InitAuxiliaryProcess. Also switch to the same pattern to advertise the
> autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
> field in shared memory. This can easily be extended to other aux
> processes in the future, if other processes need to find them.
>
> Switch to pg_atomic_uint32 for the fields. Seems easier to reason
> about than volatile pointers. There was some precedence for that, as
> were already using pg_atomic_uint32 for the procArrayGroupFirst and
> clogGroupFirst fields, which also store ProcNumbers.

> TODO: could also replace WalRecv->procno with this
> ---
>  src/backend/access/transam/xlog.c     |  3 +--
>  src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
>  src/backend/postmaster/checkpointer.c | 14 ++++---------
>  src/backend/postmaster/walwriter.c    |  6 ------
>  src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
>  src/include/storage/proc.h            |  7 ++++---
>  6 files changed, 47 insertions(+), 32 deletions(-)
>
> diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
> index 13ec6225b85..8e2e4659974 100644
> --- a/src/backend/access/transam/xlog.c
> +++ b/src/backend/access/transam/xlog.c
> @@ -2653,8 +2653,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
>
>  	if (wakeup)
>  	{
> -		volatile PROC_HDR *procglobal = ProcGlobal;
> -		ProcNumber	walwriterProc = procglobal->walwriterProc;
> +		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
>
>  		if (walwriterProc != INVALID_PROC_NUMBER)
>  			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);

It's not even clear what the volatile is trying to achieve here. Sure, it
prevents the compiler from eliding the read, but a) there's not really a
chance for that and b) it doesn't actually include a memory barrier, so before
and now this can be an outdated value...

I think that's ok, the spinlock further up provides enough of a barrier, but
it made the volatile useless anyway.




> @@ -1547,8 +1543,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
>  		on_shmem_exit(FreeWorkerInfo, 0);
>
>  		/* wake up the launcher */
> -		if (AutoVacuumShmem->av_launcherpid != 0)
> -			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
> +		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
> +		if (launcherProc != INVALID_PROC_NUMBER)
> +		{
> +			int			pid = GetPGProcByNumber(launcherProc)->pid;
> +

Maybe we should wrap that combination into a helper? Although I guess we're
going to try to get rid of the signals later anyway...


> @@ -709,6 +710,14 @@ InitAuxiliaryProcess(void)
>  	 */
>  	PGSemaphoreReset(MyProc->sem);
>
> +	/* Some aux processes are also advertised in ProcGlobal */
> +	if (MyBackendType == B_AUTOVAC_LAUNCHER)
> +		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
> +	if (MyBackendType == B_WAL_WRITER)
> +		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
> +	if (MyBackendType == B_CHECKPOINTER)
> +		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
> +

This would look cleaner if we went with my proposal for a generic
ProcGlobal->auxProc[proctype] approach :)


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

I think it could be useful. There are some cases where we wake up too often to
effectively do nothing, because we immediately clear the latch after being
woken up, which then leads to being signalled again. If we avoid really waking
up and processing/clearing interrupt bits, we'd not get signalled again.


> diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
> index 89e187425cc..6be5ca9c183 100644
> --- a/contrib/pg_prewarm/autoprewarm.c
> +++ b/contrib/pg_prewarm/autoprewarm.c
> @@ -30,17 +30,15 @@
>
>  #include "access/relation.h"
>  #include "access/xact.h"
> +#include "ipc/interrupt.h"
>  #include "pgstat.h"
>  #include "postmaster/bgworker.h"
> -#include "postmaster/interrupt.h"

I'd do this part in a separate patch. Just because this commit is quite large
and anything that we can merge earlier on is a win.


> @@ -170,10 +168,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();

I also wonder if the whole signal handler replacement bit would better be done
in a followup commit.


>  	/* 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);
> -		}

This is probably for later, but I wonder if we ought to handle the
pending-config-reload by reacting to it via CFI(), and, in the processes that
don't want to do so immediately, defer processing it by masking the interrupt
most of the time.

Part of the motivation is this:

The current handling in backend processes IMO is somewhat wrong: We don't
reload the config until the next command arrives - which means that if you
e.g. adjust the idle session timeout, the new value won't be applied until the
next command arrives.


> @@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
>  		 * pending, break out of the loop and deal with the situation below.
>  		 * Set result = false because we must restart the insertion if the
>  		 * interrupt isn't a query-cancel-or-die case.
> +		 *
> +		 * FIXME: CheckForInterruptsMask covers more than just query cancel
> +		 * and die.  Could we be more precise here?
>  		 */
> -		if (INTERRUPTS_PENDING_CONDITION())
> +		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
>  		{
>  			result = false;
>  			break;

I'm not quite following - previously we looked at InterruptPending, now we
check if any interrupt in CheckForInterruptsMask is set. Neither is just query
cancel and die, right?


> @@ -4500,11 +4473,11 @@ CheckForStandbyTrigger(void)
>  	if (LocalPromoteIsTriggered)
>  		return true;
>
> -	if (IsPromoteSignaled() && CheckPromoteSignal())
> +	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
>  	{
>  		ereport(LOG, (errmsg("received promote request")));
>  		RemovePromoteSignalFiles();
> -		ResetPromoteSignaled();
> +		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
>  		SetPromoteIsTriggered();
>  		return true;
>  	}

I think we need to clear the pending INTERRUPT_CHECK_PROMOTE regardless of
CheckPromoteSignal() being set, otherwise we'll do this over and over. In the
case of WaitForWALToBecomeAvailable() I think it'd turn into a busy loop.


> @@ -4542,7 +4515,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);
>  }
>
>  /*

> @@ -298,14 +298,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]);
>

Orthogonal: Kinda wondering if interupts (or latches) are really the right
mechanism for this, it's not just the startup process that can be interested
in new WAL arriving.  Recovery readahead, logical decoding, wait-for-lsn,
eventually the WAL writer (once we fix the stupidiy of walreceiver always
fsyncing)...

This is kinda a poor person's emulation of a condition variable.


> @@ -146,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 (;;)
> @@ -163,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)

Not entirely sure the removal of the second CFI() is quite right - if you got both
an interrupt and a timeout (not likely, I know), you'd not process the
interupt anymore, because of the "Done waiting?" block.


> @@ -2571,12 +2510,13 @@ HandleNotifyInterrupt(void)
>  void
>  ProcessNotifyInterrupt(bool flush)
>  {
> -	if (IsTransactionOrTransactionBlock())
> -		return;					/* not really idle */
> +	Assert(!IsTransactionOrTransactionBlock());
>
> -	/* Loop in case another signal arrives while sending messages */
> -	while (notifyInterruptPending)
> +	/* Loop in case another interrupt arrives while sending messages */
> +	do
> +	{
>  		ProcessIncomingNotify(flush);
> +	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
>  }

At first I thought this would do one ProcessIncomingNotify() too many. But I
guess the logic is that the caller would already have cleared the interrupt
bit?  A bit subtle without a comment.


> @@ -2430,8 +2430,7 @@ vacuum_delay_point(bool is_analyze)
>  	/* Always check for interrupts */
>  	CHECK_FOR_INTERRUPTS();
>
> -	if (InterruptPending ||
> -		(!VacuumCostActive && !ConfigReloadPending))
> +	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
>  		return;
>
>  	/*
> @@ -2440,9 +2439,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();
>  	}

I was worried for a moment that this would end up leading to busy looping if
INTERRUPT_CONFIG_RELOAD is pending, while not in an autovac process. But I
guess it's ok, because the sleep is done via pg_usleep() (gah), which won't
cut its sleep short if there are pending 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.

I wonder if HOLD_INTERRUPTS() really makes sense anymore. ISTM that a good bit
of the time we really want to disable some interrupts from being
processed. This patch allows for that - but does that by basically
saving/restoring the interrupt mask in user code.

Perhaps we should make interrupt masking be more stack like? So that you can
do something like

PUSH_INTERRUPT_MASK(QUERY_CANCEL);
/* code that doesn't want query cancellation */
POP_INTERRUPT_MASK();

That would require a stack of masks, instead of just a counter, but I think
that may be ok? We'd only very rarely need to extend it.


> +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

It's not clear to me what synchronously and asynchronously means here.


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

Hm, seems a bit weird to have a bespoke mechanism for bgworkers. But I guess
they'd need special infrastructure anyway, as, even if postmaster knew the
pgprocs of child processes, the launcher's proc number can't directly be
inferred from the bgworker's proc.


> +TODO: Open questions
> +====================
> +
> +TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
> +----------------------------------------------------
> +
> +Postmaster cannot send interrupts to processes without PGPROC
> +entries. Hence, it still uses plain Unix signals. Would be nice if
> +postmaster could send interrupts directly. If we moved the interrupt
> +mask to PMChildSlot, it could.  However, PGPROC seems like a more
> +natural location otherwise.
> +
> +Options:
> +
> +a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
> +   to processes also to processes that don't have a PGPROC entry.
> +
> +b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
> +   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
> +   and if not, use the pendingInterrupts field in PMChildSlot instead.

Hm. Do we actually ever process interrupts before we have a pgproc? I think we
have just about all signals masked before that?


> +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

e) update PMChildSlot in Init[Auxiliary]Process(), [Auxiliary]ProcKill(), so
   that postmaster knows how to get the PGPROC?


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

How about we make that session ID something like a combination of the proc
number and a generation? It seems useful to be able to efficiently get the
proc number from the session id.


> +- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
> +  it's nevertheless more instructions than it used to be.

Yea, it looks like it's a bit more expensive now. In an optimized build it
boils down to:

/* load address of CheckForInterruptsMask & MyPendingInterrupts */
mov    $0x1404c48,%r15
mov    $0x141cdc8,%rax
/* load value of CheckForInterruptsMask & MyPendingInterrupts */
mov    (%rax),%rdx
mov    (%r15),%rax
/* test if CheckForInterruptsMask == *MyPendingInterrupts */
test   %rdx,(%rax)
jne    160

The two address loads are annoying. I think you should put the various
interrupt related variables into one struct. That way both variables can be
loaded with from one address (with an offset in the load), making the code
denser and reducing register pressure a bit.  I checked and the code is a bit
nicer that way.

The indirection via MyPendingInterrupts is new overhead, but I don't
immediately have a better idea.


> +/*
> + * Currently installed interrupt handlers
> + */
> +static pg_interrupt_handler_t interrupt_handlers[64];

Probably worth defining that somewhere, instead of using a plain 64 here.


> +/*
> + * XXX: is 'volatile' still needed on all the variables below? Which ones are
> + * accessed from signal handlers?
> + */

There's different reasons volatile is needed for variables in signal handlers:

1) for modifications outside of signal handlers, the modification needs to
   actually be written to memory, instead of just being kept in a register

2) for modifications inside signal handlers, the code outside of signal
   handlers needs to always read the variable from memory, instead of
   potentially from a register.

I don't think we have to worry about 2) anymore, as the only variable that is
modified from within signal handlers is MyPendingInterrupts, and that already
ensures that the data is immediately written to memory by using an atomic.

For 1), I don't think we care either, because afaict we don't read any of the
relevant variables in signal handlers?


> +/*
> + * 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)
> +{

Hm, I think we should assert that interruptMask only has a single bit set?
Because i don't think this will work right if multiple bits are set? And then
perhaps rename the argument, so that it's clearer that we're just dealing with
a single interrupt?


> +	/*
> +	 * 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;
> +		}
> +	}
> +}
> +

Maybe I'm daft, but isn't the relevant offset in interrupt_handlers[] the bit
that is being set? So pg_rightmost_one_pos64() should do the trick? And
pg_popcount64() for detecting if there is more than one bit set.



> +/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
> +void
> +EnableInterrupt(InterruptMask interruptMask)
> +{

I guess this actually does work fine if multiple bits are set...


> +/*
> + * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
> + *
> + * If an interrupt condition is pending, and it's safe to service it,
> + * then clear the flag and call the interrupt handler.
> + *
> + * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
> + * ProcessInterrupts is guaranteed to clear the given interrupt before
> + * returning, if it was set when entering.  (This is not the same as
> + * guaranteeing that it's still clear when we return; another interrupt could
> + * have arrived.  But we promise that any pre-existing one will have been
> + * serviced.)
> + */
> +void
> +ProcessInterrupts(void)
> +{
> +	InterruptMask interruptsToProcess;
> +
> +	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);

Perhaps also assert that CheckForInterruptsMask isn't 0?


> +
> +/*
> + * Switch to local interrupts.  Other backends can't send interrupts to this
> + * one.  Only RaiseInterrupt() can set them, from inside this process.
> + */

When do we actually need this? During BackendInitialize() the signal handlers
that are set up just do _exit(1) and thus don't need to signal
interrupts. After BackendInitialize(), but before InitProcess() all signals
are masked.

I guess it may not really matter, because we will still need the indirection
to PGPROC->interruptMask during CFI().


> +void
> +SwitchToLocalInterrupts(void)
> +{
> +	if (MyPendingInterrupts == &LocalPendingInterrupts)
> +		return;

ISTM that should be an assert, because something has seriously gone wrong if
that ever happens.


> +	MyPendingInterrupts = &LocalPendingInterrupts;
> +
> +	/*
> +	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
> +	 * seeing the new MyPendingInterrupts destination.
> +	 */
> +	pg_memory_barrier();

I don't think that can ever require a memory barrier - at most a compiler
barrier. Signal handlers and the main execution thread run with the same view
of the memory. The only possible inconsistency is when values are in
registers, rather than reading from memory.


> +	/*
> +	 * Mix in the interrupts that we have received already in our shared
> +	 * interrupt vector, while atomically clearing it.  Other backends may
> +	 * continue to set bits in it after this point, but we've atomically
> +	 * transferred the existing bits to our local vector so we won't get
> +	 * duplicated interrupts later if we switch back.
> +	 */

Switch back? We should never do that, right?


> +/*
> + * Set an interrupt flag in this backend.
> + *
> + * Note: This is called from signal handlers, so needs to be async-signal
> + * safe!
> + */
> +void
> +RaiseInterrupt(InterruptMask interruptMask)
> +{
> +	uint64		old_pending;
> +
> +	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
> +
> +	/*
> +	 * If the process is currently blocked waiting for an interrupt to arrive,
> +	 * and the interrupt wasn't already pending, wake it up.
> +	 */
> +	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
> +		WakeupMyProc();
> +}

FWIW, on x86 fetch_or()/fetch_and() are more costly when the old value is
returned (because "lock or" doesn't return the old value, so it has to be
implemented as a CAS loop). And CAS loops are considerably more expensive than
a single atomic op.

I don't think SLEEPING_ON_INTERRUPTS could be set while RaiseInterrupt() is
running, so it should be ok to just read the variable to check for that.

Come to that, I think RaiseInterrupt() could use a non-modifying read to see
if the bit is already set and just not do an atomic op in that case? That
obviously won't work in SendInterrupt(), but here it may?


Could we have the mask of interrupts that WaitInterrupt() is waiting for in a
second variable? That way we could avoid interrupting WaitInterrupt() when
raising or sending a signal that WaitInterrupt() is not waiting for.  I think
that can be one race-freely with a bit of care?


> +/*
> + * 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?
> + */

We could wrap InteruptMask or ProcNumber in a struct, but both seem somewhat
painful :(.


> +void
> +SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
> +{

Should this actually be a mask, ather than a single value? Not sure I see
cases where it'd make sense to send multiple interrupts at once.


> +/*
> + * 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.

I think it's somewhat confusing that you can wait for interrupts, without
waiting for interrupts.  And having interrupts and wakeEvents in two
consecutive arguments also seems somewhat confusing :(.

When would you use WaitInterrupt() without WL_INTERRUPT?  Ugh, seems we have
some cases that use WaitInterruptOrSocket(0) during connection establishment.
We really ought to fix that, but this patch is probably not the time to do so.


> +/* 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;
> +}

Hm. I think this either would need to ensure its only run in postmaster, or
have nextAddinInterruptBit in shared memory? Otherwise two extensions loaded
dynamically in different processes will get the same bit?


> diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
> new file mode 100644
> index 00000000000..589f755622a
> --- /dev/null
> +++ b/src/backend/ipc/signal_handlers.c
> @@ -0,0 +1,160 @@
> +/*-------------------------------------------------------------------------
> + *
> + * signal_handlers.c
> + *	  Standard signal handlers.

Why not introduce this in a preparatory commit?


> +/*
> + * Set the standard signal handlers suitable for most postmaster child
> + * processes.
> + *
> + * Note: this doesn't unblock the signals yet. You can make additional
> + * pqsignal() calls to modify the default behavior before unblocking.
> + */
> +void
> +SetPostmasterChildSignalHandlers(void)
> +{

PostgresMain() seems to redo a bunch of this? And not just for single user
mode, but always?


> --- a/src/backend/meson.build
> +++ b/src/backend/meson.build
> @@ -15,6 +15,7 @@ subdir('catalog')
>  subdir('commands')
>  subdir('executor')
>  subdir('foreign')
> +subdir('ipc')
>  subdir('jit')
>  subdir('lib')
>  subdir('libpq')

I'm all on board with a top-level ipc dir, but it seems somewhat odd to have
two ipc directories...


> +	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
> +	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);

Hm. What if a second INTERRUPT_CONFIG_RELOAD arrives while autovac is in the
middle of ProcessAutoVacLauncherConfigReloadInterrupt? If there is any CFI()
burried within that, it could lead to
ProcessAutoVacLauncherConfigReloadInterrupt() being re-entered.

I think we may need to block interrupts while calling callbacks in
ProcessInterrupts().



Ran out of time & energy at this point...

But had already noticed this:

>
> -			pgaio_debug_io(DEBUG4, staged_ios[i],
> +			pgaio_debug_io(LOG, staged_ios[i],
>  						   "choosing worker %d",
>  						   worker);
>  		}
>  	}

Probably unintentional to have the s/DEBUG4/LOG/ here :)


Greetings,

Andres Freund






^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
@ 2026-02-18 00:11               ` Heikki Linnakangas <[email protected]>
  2026-02-20 14:22                 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 1 reply; 22+ messages in thread

From: Heikki Linnakangas @ 2026-02-18 00:11 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

Many thanks for the review! Here's a new version addressing these two 
issues:

- 64-bit atomics simulation code being unsafe in signal handlers
- CHECK_FOR_INTERRUPTS() getting more expensive than before

The rest of your comments make sense at quick glance, but I'm still 
processing.

For easier review, these changes vs to the previous patch version are in 
the last patch in the patch set, as a separate patch.

On 14/02/2026 23:56, Andres Freund wrote:
> On 2026-02-13 19:11:52 +0200, Heikki Linnakangas wrote:
>> - Switched to 64-bit interrupt masks. This gives more headroom for
>> extensions to reserve their own custom interrupts
> 
> I don't think as done here that's quite legal - we can't rely on 64bit atomics
> inside signal handlers, because they may be emulated with a spinlock. Which in
> turn means that interrupting oneself while modifying a 64bit atomic could
> result in a self-deadlock.

Argh.

> It may be possible to make it safe to do the 64bit atomics emulation signal
> safe, but I think the complexity and the overhead would likely make that
> problematic.
> 
> I guess I'd just emulate it by splitting the interrupt mask into two? Perhaps
> with a special interrupt bit set in the first atomic indicating that the
> second word has pending interrupts?

Makes sense. I hate having bespoken code like that for ancient 
platforms, though. It will get very little testing.

>> +- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
>> +  it's nevertheless more instructions than it used to be.
> 
> Yea, it looks like it's a bit more expensive now. In an optimized build it
> boils down to:
> 
> /* load address of CheckForInterruptsMask & MyPendingInterrupts */
> mov    $0x1404c48,%r15
> mov    $0x141cdc8,%rax
> /* load value of CheckForInterruptsMask & MyPendingInterrupts */
> mov    (%rax),%rdx
> mov    (%r15),%rax
> /* test if CheckForInterruptsMask == *MyPendingInterrupts */
> test   %rdx,(%rax)
> jne    160
> 
> The two address loads are annoying. I think you should put the various
> interrupt related variables into one struct. That way both variables can be
> loaded with from one address (with an offset in the load), making the code
> denser and reducing register pressure a bit.  I checked and the code is a bit
> nicer that way.
> 
> The indirection via MyPendingInterrupts is new overhead, but I don't
> immediately have a better idea.

I tried a new approach in the attached. There's a new flag, 
CFI_ATTENTION, which is set whenever *any* interrupt bit is set, and 
cleared by ProcessInterrupts(). That way CHECK_FOR_INTERRUPTS() can 
check just the constant flag, without loading CheckForInterruptsMask. 
The CheckForInterruptsMask check is offloaded to the ProcessInterrupts() 
slow path.

That reduces the CHECK_FOR_INTERRUPTS() to:

/* load address of MyPendingInterrupts->flags */
lea    0x433f0f(%rip),%rax        # 0x9728a0 <MyPendingInterrupts>
/* load value of MyPendingInterrupts->flags */
mov    (%rax),%rax
/* test if MyPendingInterrupts->flags == 0 */
cmpl   $0x0,(%rax)
/* jump for ProcessInterrupts */
jne    0x53e9fd <makeItemUnary+173>

The downside from a performance point of view is that whenever a new 
interrupt arrives, the next CHECK_FOR_INTERRUPTS() needs to call 
ProcessInterrupts(), whether or not it's in CheckForInterruptsMask. 
However, if the same interrupt is sent again and it still hasn't been 
processed, the flag won't be set again, so you don't incur the penalty 
repeatedly.

>> +/*
>> + * Set an interrupt flag in this backend.
>> + *
>> + * Note: This is called from signal handlers, so needs to be async-signal
>> + * safe!
>> + */
>> +void
>> +RaiseInterrupt(InterruptMask interruptMask)
>> +{
>> +	uint64		old_pending;
>> +
>> +	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
>> +
>> +	/*
>> +	 * If the process is currently blocked waiting for an interrupt to arrive,
>> +	 * and the interrupt wasn't already pending, wake it up.
>> +	 */
>> +	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
>> +		WakeupMyProc();
>> +}
> 
> FWIW, on x86 fetch_or()/fetch_and() are more costly when the old value is
> returned (because "lock or" doesn't return the old value, so it has to be
> implemented as a CAS loop). And CAS loops are considerably more expensive than
> a single atomic op.
> 
> I don't think SLEEPING_ON_INTERRUPTS could be set while RaiseInterrupt() is
> running, so it should be ok to just read the variable to check for that.
> 
> Come to that, I think RaiseInterrupt() could use a non-modifying read to see
> if the bit is already set and just not do an atomic op in that case? That
> obviously won't work in SendInterrupt(), but here it may?
> 
> 
> Could we have the mask of interrupts that WaitInterrupt() is waiting for in a
> second variable? That way we could avoid interrupting WaitInterrupt() when
> raising or sending a signal that WaitInterrupt() is not waiting for.  I think
> that can be one race-freely with a bit of care?

Yeah, I thought of that, but I'm not sure what the right tradeoff here 
is. I doubt the spurious wakeups matter much in practice. Then again, 
maybe it's not much more complicated, so maybe I should try that.

Now with this new version, the same consideration applies to the 
CFI_ATTENTION flag I added. We could expose a process's 
CheckForInterruptsMask alongside the pending interrupts, so it would be 
SendInterrupt()'s responsibility to check if the receiving backend's 
CheckForInterruptsMask includes the interrupt that's being sent. That 
would similarly eliminate the "false positive" ProcessInterrupts() calls 
from CHECK_FOR_INTERRUPTS(), by moving the logic to the senders. The 
SLEEPING_ON_INTERRUPTS and CFI_ATTENTION flags are quite symmetrical.

- Heikki

Attachments:

  [text/x-patch] v10-0001-Centralize-resetting-SIGCHLD-handler.patch (9.7K, ../../[email protected]/2-v10-0001-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From bbda9557afac1e881a86a08de9d6e21ce56986cc Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Jan 2026 20:22:43 +0200
Subject: [PATCH v10 1/5] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6fde740465f..89c2b4f9500 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -411,7 +411,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1422,7 +1421,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 261ccd3f59c..d4031ac3787 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -795,7 +795,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0956bd39a85..dbd670c16cc 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -108,11 +108,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e03c19123bc..03aefa5b670 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -220,11 +220,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 82731e452fc..10a5ef9e15c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -237,9 +237,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cdbe53dd262..330c10ddb0b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -234,11 +234,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 86c5e376b40..8515bfb9653 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -283,11 +283,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 742137edad6..4e4eed4d076 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -263,11 +263,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 7c0e2809c17..8ca7b07e947 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -108,11 +108,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 062a08ccb88..5e1ee7e4728 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1536,7 +1536,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 10e64a7d1f4..352a3f31e95 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -257,9 +257,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2cde8ebc729..5acc8ee3ac8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3743,9 +3743,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 21de158adbb..b88ce440b1d 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4285,13 +4285,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
-									 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 03f6c8479f2..fadf856d9bc 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -142,6 +142,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -154,6 +163,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] v10-0002-Use-standard-die-handler-for-SIGTERM-in-bgworker.patch (5.3K, ../../[email protected]/3-v10-0002-Use-standard-die-handler-for-SIGTERM-in-bgworker.patch)
  download | inline diff:
From 51ef64973b747bb563c425ef2b717677ba24dc35 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 17 Feb 2026 23:04:26 +0200
Subject: [PATCH v10 2/5] Use standard die() handler for SIGTERM in bgworkers

The previous default bgworker_die() signal would exit with elog(FATAL)
directly from the signal handler. That could cause deadlocks or
crashes if the signal handler runs while we're e.g holding a spinlock
or in the middle of a memory allocation.

All the built-in background workers overrode that to use the normal
die() handler and CHECK_FOR_INTERRUPTS(). Let's make that the default
for all background workers. Some extensions relying on the old
behavior might need to adapt, but the new default is much safer and
the right thing to do for most background workers.
---
 doc/src/sgml/bgworker.sgml                       | 10 ++++++++++
 src/backend/access/transam/parallel.c            |  1 -
 src/backend/postmaster/bgworker.c                | 16 +---------------
 .../replication/logical/applyparallelworker.c    |  1 -
 src/backend/replication/logical/launcher.c       |  1 -
 src/backend/replication/logical/worker.c         |  1 -
 6 files changed, 11 insertions(+), 19 deletions(-)

diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 4699ef6345f..2affba74382 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -232,6 +232,8 @@ typedef struct BackgroundWorker
   </para>
 
   <para>
+   A well-behaved background worker must react promptly to standard signals
+   that the postmaster uses to control its child processes.
    Signals are initially blocked when control reaches the
    background worker's main function, and must be unblocked by it; this is to
    allow the process to customize its signal handlers, if necessary.
@@ -240,6 +242,14 @@ typedef struct BackgroundWorker
    <function>BackgroundWorkerBlockSignals</function>.
   </para>
 
+  <para>
+   The default signal handlers merely set interrupt flags
+   that are processed later by <function>CHECK_FOR_INTERRUPTS()</function>.
+   <function>CHECK_FOR_INTERRUPTS()</function> should be called in any
+   long-running loop to ensure that the background worker doesn't prevent the
+   system from shutting down in a timely fashion.
+  </para>
+
   <para>
    If <structfield>bgw_restart_time</structfield> for a background worker is
    configured as <literal>BGW_NEVER_RESTART</literal>, or if it exits with an exit
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index fe00488487d..44786dc131f 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -1327,7 +1327,6 @@ ParallelWorkerMain(Datum main_arg)
 	InitializingParallelWorker = true;
 
 	/* Establish signal handlers. */
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/* Determine and set our parallel worker number. */
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index d4031ac3787..133af5aee97 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -718,20 +718,6 @@ SanityCheckBackgroundWorker(BackgroundWorker *worker, int elevel)
 	return true;
 }
 
-/*
- * Standard SIGTERM handler for background workers
- */
-static void
-bgworker_die(SIGNAL_ARGS)
-{
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
-
-	ereport(FATAL,
-			(errcode(ERRCODE_ADMIN_SHUTDOWN),
-			 errmsg("terminating background worker \"%s\" due to administrator command",
-					MyBgworkerEntry->bgw_type)));
-}
-
 /*
  * Main entry point for background worker processes.
  */
@@ -787,7 +773,7 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, bgworker_die);
+	pqsignal(SIGTERM, die);
 	/* SIGQUIT handler was already set up by InitPostmasterChild */
 	pqsignal(SIGHUP, SIG_IGN);
 
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 8a01f16a2ca..1730ace5490 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -879,7 +879,6 @@ ParallelApplyWorkerMain(Datum main_arg)
 	 * receiving SIGTERM.
 	 */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 	BackgroundWorkerUnblockSignals();
 
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 3ed86480be2..e6112e11ec2 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -1213,7 +1213,6 @@ ApplyLauncherMain(Datum main_arg)
 
 	/* Establish signal handlers. */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 32725c48623..75af3a71ca8 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -5890,7 +5890,6 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	/* Setup signal handling */
 	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
-- 
2.47.3



  [text/x-patch] v10-0003-Refactor-how-some-aux-processes-advertise-their-.patch (9.6K, ../../[email protected]/4-v10-0003-Refactor-how-some-aux-processes-advertise-their-.patch)
  download | inline diff:
From 43b0fc2a8b524f17aa38cd8f07c602aaf64ea93f Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 14:23:35 +0200
Subject: [PATCH v10 3/5] Refactor how some aux processes advertise their
 ProcNumber

This moves the responsibility of setting the
ProcGlobal->walrewriterProc and checkpointerProc fields to
InitAuxiliaryProcess. Also switch to the same pattern to advertise the
autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
field in shared memory. This can easily be extended to other aux
processes in the future, if other processes need to find them.

Switch to pg_atomic_uint32 for the fields. Seems easier to reason
about than volatile pointers. There was some precedence for that, as
were already using pg_atomic_uint32 for the procArrayGroupFirst and
clogGroupFirst fields, which also store ProcNumbers.

TODO: could also replace WalRecv->procno with this
---
 src/backend/access/transam/xlog.c     |  3 +--
 src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
 src/backend/postmaster/checkpointer.c | 14 ++++---------
 src/backend/postmaster/walwriter.c    |  6 ------
 src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
 src/include/storage/proc.h            |  7 ++++---
 6 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13ec6225b85..8e2e4659974 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2653,8 +2653,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 
 	if (wakeup)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	walwriterProc = procglobal->walwriterProc;
+		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89c2b4f9500..0a730fb5b45 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -277,7 +277,6 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -293,7 +292,6 @@ typedef struct AutoVacuumWorkItem
 typedef struct
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -563,8 +561,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		proc_exit(0);			/* done */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
-
 	/*
 	 * Create the initial database list.  The invariant we want this list to
 	 * keep is that it's ordered by decreasing next_worker.  As soon as an
@@ -798,8 +794,6 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
-
 	proc_exit(0);				/* done */
 }
 
@@ -1529,6 +1523,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (AutoVacuumShmem->av_startingWorker != NULL)
 	{
+		ProcNumber	launcherProc;
+
 		MyWorkerInfo = AutoVacuumShmem->av_startingWorker;
 		dbid = MyWorkerInfo->wi_dboid;
 		MyWorkerInfo->wi_proc = MyProc;
@@ -1547,8 +1543,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
+		if (launcherProc != INVALID_PROC_NUMBER)
+		{
+			int			pid = GetPGProcByNumber(launcherProc)->pid;
+
+			if (pid != 0)
+				kill(pid, SIGUSR2);
+		}
 	}
 	else
 	{
@@ -3391,7 +3393,6 @@ AutoVacuumShmemInit(void)
 
 		Assert(!found);
 
-		AutoVacuumShmem->av_launcherpid = 0;
 		dclist_init(&AutoVacuumShmem->av_freeWorkers);
 		dlist_init(&AutoVacuumShmem->av_runningWorkers);
 		AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 03aefa5b670..978a7d0e649 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,6 +47,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
@@ -343,12 +344,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	UpdateSharedMemoryConfig();
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->checkpointerProc = MyProcNumber;
-
 	/*
 	 * Loop until we've been asked to write the shutdown checkpoint or
 	 * terminate.
@@ -1120,7 +1115,7 @@ RequestCheckpoint(int flags)
 	for (ntries = 0;; ntries++)
 	{
 		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 		if (checkpointerProc == INVALID_PROC_NUMBER)
 		{
@@ -1261,8 +1256,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	/* ... but not till after we release the lock */
 	if (too_full)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
@@ -1543,7 +1537,7 @@ void
 WakeupCheckpointer(void)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
-	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
 		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 8ca7b07e947..1851417ad1b 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -200,12 +200,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	hibernating = false;
 	SetWalWriterSleeping(false);
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->walwriterProc = MyProcNumber;
-
 	/*
 	 * Loop forever
 	 */
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index fd8318bdf3d..ba7eaed1a13 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -211,8 +211,9 @@ InitProcGlobal(void)
 	dlist_init(&ProcGlobal->bgworkerFreeProcs);
 	dlist_init(&ProcGlobal->walsenderFreeProcs);
 	ProcGlobal->startupBufferPinWaitBufId = -1;
-	ProcGlobal->walwriterProc = INVALID_PROC_NUMBER;
-	ProcGlobal->checkpointerProc = INVALID_PROC_NUMBER;
+	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->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -709,6 +710,14 @@ InitAuxiliaryProcess(void)
 	 */
 	PGSemaphoreReset(MyProc->sem);
 
+	/* Some aux processes are also advertised in ProcGlobal */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
+	if (MyBackendType == B_WAL_WRITER)
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
+	if (MyBackendType == B_CHECKPOINTER)
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+
 	/*
 	 * Arrange to clean up at process exit.
 	 */
@@ -1053,6 +1062,23 @@ AuxiliaryProcKill(int code, Datum arg)
 	SwitchBackToLocalLatch();
 	pgstat_reset_wait_event_storage();
 
+	/* If this was one of aux processes advertised in ProcGlobal, clear it */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->avLauncherProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_WAL_WRITER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walwriterProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_CHECKPOINTER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	}
+
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 23e5cd98161..1f1ab7d02ca 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,11 +445,12 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 clogGroupFirst;
 
 	/*
-	 * Current slot numbers of some auxiliary processes. There can be only one
+	 * Current proc numbers of some auxiliary processes. There can be only one
 	 * of each of these running at a time.
 	 */
-	ProcNumber	walwriterProc;
-	ProcNumber	checkpointerProc;
+	pg_atomic_uint32 avLauncherProc;
+	pg_atomic_uint32 walwriterProc;
+	pg_atomic_uint32 checkpointerProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
-- 
2.47.3



  [text/x-patch] v10-0004-Replace-Latches-with-Interrupts.patch (498.8K, ../../[email protected]/5-v10-0004-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 378f56a81308948a4cc7be9ab766932009204f69 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Feb 2026 17:07:28 +0200
Subject: [PATCH v10 4/5] Replace Latches with Interrupts

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

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

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

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

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

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

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() clears the interrupts bits that it
processes, the caller is no longer required to do ClearInterrupt (or
ResetLatch) for those bits. If you wake up for other interrupts that
are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear
those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_WAIT_WAKEUP interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

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

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

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

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/[email protected]
  Discussion: https://www.postgresql.org/message-id/[email protected]

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

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

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

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

XXX: original text from Thomas's patch
--------------------------------------

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

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

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 89e187425cc..6be5ca9c183 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -170,10 +168,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. */
@@ -222,27 +223,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
 		{
@@ -266,14 +267,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);
 	}
 
 	/*
@@ -423,7 +423,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;
 
 		/*
@@ -444,7 +444,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)));
@@ -504,8 +504,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. */
@@ -923,9 +922,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),
@@ -958,9 +954,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 487a1a23170..3bf73e4d2a9 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -829,8 +829,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);
@@ -1483,7 +1483,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))
@@ -1578,7 +1578,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))
@@ -1686,12 +1686,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 				pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 			/* Sleep until there's something to do */
-			wc = WaitLatchOrSocket(MyLatch,
-								   WL_LATCH_SET | WL_SOCKET_READABLE |
-								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								   PQsocket(conn),
-								   cur_timeout, pgfdw_we_cleanup_result);
-			ResetLatch(MyLatch);
+			wc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_INTERRUPT | WL_SOCKET_READABLE |
+									   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									   PQsocket(conn),
+									   cur_timeout, pgfdw_we_cleanup_result);
 
 			CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 60d90329a65..eb925fda39e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2affba74382..06bc57f10bd 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -63,7 +63,7 @@ typedef struct BackgroundWorker
     char        bgw_function_name[BGW_MAXLEN];
     Datum       bgw_main_arg;
     char        bgw_extra[BGW_EXTRALEN];
-    pid_t       bgw_notify_pid;
+    pid_t       bgw_notify_pid;			/* not used */
 } BackgroundWorker;
 </programlisting>
   </para>
@@ -131,6 +131,21 @@ typedef struct BackgroundWorker
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>BGWORKER_NO_NOTIFY</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BGWORKER_NO_NOTIFY</primary></indexterm> Normally,
+       the backend that registers a dynamic worker will be notified
+       with <literal>INTERRUPT_WAIT_WAKEUP</literal> when the worker's state
+       changes, which allows the caller to wait for the worker to start and
+       shut down.  That can be suppressed by setting this flag.
+       <!-- FIXME: Should we reserve a dedicated INTERRUPT bit for it? And if
+            we do, do we even need the BGWORKER_NO_NOTIFY flag, there's very little
+            overhead in sending a useless interrupt -->
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -204,12 +219,8 @@ typedef struct BackgroundWorker
   </para>
 
   <para>
-   <structfield>bgw_notify_pid</structfield> is the PID of a PostgreSQL
-   backend process to which the postmaster should send <literal>SIGUSR1</literal>
-   when the process is started or exits.  It should be 0 for workers registered
-   at postmaster startup time, or when the backend registering the worker does
-   not wish to wait for the worker to start up.  Otherwise, it should be
-   initialized to <literal>MyProcPid</literal>.
+   <structfield>bgw_notify_pid</structfield> is not used and may be removed
+   in a future release.
   </para>
 
   <para>Once running, the process can connect to a database by calling
@@ -260,7 +271,7 @@ typedef struct BackgroundWorker
    reinitializes the cluster due to a backend failure.  Backends which need
    to suspend execution only temporarily should use an interruptible sleep
    rather than exiting; this can be achieved by calling
-   <function>WaitLatch()</function>. Make sure the
+   <function>WaitInterrupt()</function>. Make sure the
    <literal>WL_POSTMASTER_DEATH</literal> flag is set when calling that function, and
    verify the return code for a prompt exit in the emergency case that
    <command>postgres</command> itself has terminated.
@@ -291,10 +302,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 05642dc02e3..98819087871 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..e8e84e248ce 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index 436e54f2066..426259935e7 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index 7a6b177977b..55b106d340d 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index d205093e21d..da2c946c7d1 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 11a6674a10b..2a667d65839 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index dfffce3e396..9fc03134804 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 11b214eb99b..9e236799f2e 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 632c2427952..9cb4b766795 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 4be267ff657..ae7e7993c8b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,6 +143,7 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
@@ -3300,11 +3301,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 3047bd46def..da1952e9412 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..70a4e4ee00a 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 44786dc131f..65c467f25f9 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -114,9 +115,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -126,9 +124,6 @@ static FixedParallelState *MyFixedParallelState;
 /* List of active parallel contexts. */
 static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list);
 
-/* Backend-local copy of data from FixedParallelState. */
-static pid_t ParallelLeaderPid;
-
 /*
  * List of internal parallel worker entry points.  We need this for
  * reasons explained in LookupParallelWorkerFunction(), below.
@@ -242,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -611,7 +606,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.
@@ -766,16 +760,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();
+				}
 			}
 		}
 
@@ -884,15 +883,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1033,21 +1033,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1082,9 +1067,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1326,7 +1309,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. */
@@ -1362,8 +1348,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));
 
@@ -1379,8 +1364,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
@@ -1619,9 +1603,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..5592df30f8d 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index eabc4d48208..2477618e870 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8e2e4659974..b6f43b359c4 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2656,7 +2656,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);
 	}
 }
 
@@ -9509,11 +9509,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2efe4105efb..8c74eceec18 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -24,11 +24,11 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.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"
@@ -718,17 +718,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index c0c2744d45b..d2b3785560c 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -322,23 +323,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.
 	 */
@@ -473,7 +457,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -547,13 +530,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).
@@ -1656,13 +1632,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);
 }
 
 /*
@@ -1792,8 +1761,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
@@ -1822,8 +1791,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))
 			{
@@ -1844,9 +1813,8 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2979,7 +2947,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;
 
@@ -3056,8 +3024,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)
@@ -3065,11 +3033,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;
 
@@ -3090,10 +3063,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;
 }
@@ -3771,16 +3746,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;
@@ -4046,11 +4021,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;
 				}
 
@@ -4066,11 +4042,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 */
@@ -4502,11 +4475,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4544,7 +4517,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);
 }
 
 /*
@@ -4748,7 +4724,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 d286ff63123..efe08c4ebfe 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -68,7 +68,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -241,9 +241,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
@@ -298,14 +298,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);
 }
@@ -364,7 +363,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.
@@ -376,7 +375,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);
@@ -434,8 +433,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
@@ -447,8 +447,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index e112eed7485..e49fdc1efa2 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 657c591618d..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  sets the process's latch, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  main loop in PostgresBackend()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,6 +166,7 @@
 #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"
@@ -177,7 +174,6 @@
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -288,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -318,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -527,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. ProcessNotifyInterrupt() will then be called whenever it's safe to
- * actually deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -552,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1265,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1360,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1386,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1618,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1638,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2242,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2262,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2293,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2304,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2330,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2340,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2360,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2371,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, signalProcnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 }
 
@@ -2530,39 +2496,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted
- *		via the process's latch, and this routine will get called.
- *		If we are truly idle (ie, *not* inside a transaction block),
- *		process the incoming notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if the interrupt occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -2571,12 +2510,13 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
-	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2686,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3050,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 94d6f415a06..5005030cd7b 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -269,9 +269,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)
@@ -303,7 +307,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03932f45c8a..cc34f40f2ac 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2430,8 +2430,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2440,9 +2439,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();
 	}
@@ -2529,8 +2528,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 7138dc692c6..166b6f5eec6 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
+#include "utils/resowner.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1043,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;
@@ -1067,8 +1067,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,
@@ -1078,8 +1078,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)
@@ -1122,14 +1122,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 4105f1d1968..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,7 +34,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "utils/wait_event.h"
 
@@ -382,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..459f3d3cae4
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,260 @@
+Interrupts
+==========
+
+"Interrupts" are a set of flags that represent conditions that should
+be handled at a later time.  They are roughly analogous to Unix
+signals, except that they are handled cooperatively by checking for
+them at many points in the code. The CHECK_FOR_INTERRUPTS() macro is
+called at strategically located spots where it is normally safe to
+process interrupts.
+
+Well behaved backend code calls CHECK_FOR_INTERRUPTS() periodically in
+any long-running loops or other long computations, and never sleeps
+using mechanisms other than the WaitEventSet mechanism or the more
+convenient WaitInterrupt / WaitSockerOrInterrupt functions (except for
+bounded short periods, eg LWLock waits). Following these rules ensures
+that the backend doesn't become uncancellable for too long.
+
+Each interrupt type can have a handler function associated with it,
+which will be called by CHECK_FOR_INTERRUPTS() if the interrupt is
+pending.  A standard set of interrupt handlers are installed at
+process startup, but they can be overridden by
+SetInterruptHandler(). A handler can also be temporarily disabled by
+calling DisableInterrupt and re-enabled by EnableInterrupt.
+
+If an interrupt is raised that doesn't have a handler function or it
+is not enabled, the interrupt will remain pending until it is cleared,
+or the handler is re-enabled.
+
+If an interrupt is sent that was already pending for the target
+process, it is coalesced, ie. the target process will process it only
+once. Attempting to set an interrupt bit that is already set is very
+fast, amounting to merely an atomic op in shared memory.
+
+See src/include/ipc/interrupt.h for a list of all the interrupts used
+in core code. Extensions can define their own interrupts.
+
+HOLD/RESUME_INTERRUPTS
+----------------------
+
+Processing an interrupt may throw an error with ereport() (for
+example, query cancellation) or terminate the process gracefully. In
+some cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level
+subroutines that might sometimes be called in contexts that do *not*
+want to allow interrupt processing.  The HOLD_INTERRUPTS() and
+RESUME_INTERRUPTS() macros allow code to ensure that no interrupt
+handlers are called, even if CHECK_FOR_INTERRUPTS() gets called in a
+subroutine.  The interrupt will be held off until
+CHECK_FOR_INTERRUPTS() is done outside any HOLD_INTERRUPTS() ...
+RESUME_INTERRUPTS() section.
+
+HOLD_INTERRUPTS() blocks can be nested. If HOLD_INTERRUPTS() is called
+multiple times, interrupts are held until every HOLD_INTERRUPTS() call
+has been balanced with a RESUME_INTERRUPTS() call.
+
+Critical sections
+-----------------
+
+A related, but conceptually distinct, mechanism is the "critical
+section" mechanism.  A critical section not only holds off cancel/die
+interrupts, but causes any ereport(ERROR) or ereport(FATAL) to become
+ereport(PANIC) --- that is, a system-wide reset is forced.  Needless
+to say, only really *critical* code should be marked as a critical
+section! This mechanism is mostly used for XLOG-related code.
+
+Sending interrupts
+------------------
+
+Interrupt flags can be "raised" in different ways:
+- synchronously by code that wants to defer an action, by calling
+  RaiseInterrupt,
+- asynchronously by timers or other signal handlers, also by calling
+  RaiseInterrupt, or
+- "sent" by other backends with SendInterrupt
+
+Processing interrupts
+---------------------
+
+Interrupts are usually handled by handler functions, which are called
+from CHECK_FOR_INTERRUPTS() when the corresponding interrupt is
+pending. Use SetInterruptHandler() to install a handler function.
+
+Sometimes you don't want an interrupt to be processed at any random
+CHECK_FOR_INTERRUPTS() invocation. In that case, you can leave the
+interrupt handler disabled, and check for the interrupt explicitly at
+suitable spots with InterruptPending().
+
+INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
+interrupt needs to be serviced, without trying to do so immediately.
+Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
+which tells whether CHECK_FOR_INTERRUPTS() is sure to clear the
+interrupt.
+
+
+Waiting for an interrupt
+------------------------
+The correct pattern to wait for an event(s) that raises an interrupt
+is:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	/* Check if our interrupt was raised (clearing it if it was) */
+	if (ConsumeInterrupt(INTERRUPT_PRINTER_ON_FIRE))
+		break;
+
+	/*
+	 * Sleep until our interrupt or one of the standard interrupts is
+	 * received.
+	 */
+	WaitInterrupt(INTERRUPT_CFI_MASK |
+				  INTERRUPT_PRINTER_ON_FIRE,
+				  ...);
+}
+```
+
+Often an interrupt is used to wake up a process that is known to be
+waiting and not doing anything else. The INTERRUPT_WAIT_WAKEUP
+interrupt is reserved for such use cases where you don't need a
+dedicated interrupt flag. When using INTERRUPT_WAIT_WAKEUP, you need
+some other signaling, e.g. a flag in shared memory, to indicate that
+the desired event has happen, as you may sometimes receive false
+wakeups. The pattern using INTERRUPT_WAIT_WAKEUP looks like this:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+}
+```
+
+It's important to clear the interrupt *before* checking if there's
+work to do. Otherwise, if someone sets the interrupt between the check
+and the ClearInterrupt() call, you will miss it and Wait will
+incorrectly block. Another valid coding pattern looks like:
+
+```
+for (;;)
+{
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+}
+```
+
+This is useful to reduce interrupt traffic if it's expected that the
+loop's termination condition will often be satisfied in the first
+iteration; the cost is an extra loop iteration before blocking when it
+is not.  What must be avoided is placing any checks for asynchronous
+events after WaitInterrupt and before ClearInterrupt, as that creates
+a race condition.
+
+Custom interrupts in extensions
+-------------------------------
+
+An extension can allocate interrupt bits for its own purposes by
+calling RequestAddinInterrupt(). Note that interrupts are a somewhat
+scarce resource, so consider using INTERRUPT_WAIT_WAKEUP if all you
+need is a simple wakeup in some loop.
+
+
+Unix Signals
+============
+
+Postmaster and most child processes also respond to a few standard
+Unix signals:
+
+SIGHUP -> Raises INTERRUPT_CONFIG_RELOAD
+SIGINT -> Raises INTERRUPT_QUERY_CANCEL
+SIGTERM -> Raises INTERRUPT_TERMINATE
+SIGQUIT -> immediate shutdown, abort the process
+
+pg_ctl uses these Unix signals to tell postmaster to reload config,
+stop, etc. These are also mentioned in the user documentation.
+
+Postmaster cannot send interrupts to processes without PGPROC entries
+(just syslogger nowadays), and it doesn't know the PGPROC entries of
+other child processes anyway. Hence, it still uses the above Unix
+signals for postmaster -> child signaling. The only exception is when
+postmaster notifies a backend that a bgworker it launched has
+exited. Postmaster sends that interrupt directly. The backend
+registers explicitly for that notification, and supplies the
+ProcNumber to postmaster when registering.
+
+There are a few more exceptions:
+
+- A few processes like the checkpointer, archiver, and IO workers
+  don't react to SIGTERM, because Unix system shutdown sends a SIGTERM
+  to all processes, and we want them to exit only only after all other
+  process. Instead, the postmaster sends them SIGUSR2 after all the
+  other processes have exited.
+
+- Child processes use SIGUSR1 to request the postmaster to do various
+  actions or notify that some event has occurred. See pmsignal.c for
+  details on that mechanism
+
+
+TODO: Open questions
+====================
+
+TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
+----------------------------------------------------
+
+Postmaster cannot send interrupts to processes without PGPROC
+entries. Hence, it still uses plain Unix signals. Would be nice if
+postmaster could send interrupts directly. If we moved the interrupt
+mask to PMChildSlot, it could.  However, PGPROC seems like a more
+natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+d) Keep it as it is, continue to use signals for postmaster -> child
+   signaling
+
+
+TODO: Unique session id
+----------------------
+
+In some places, we read the pid of a process (from PGPROC or
+elsewhere), and send signal to it, accepting that the process may have
+already exited.  If we directly replace those uses of 'pid' with a
+ProcNumber, the ProcNumber might get reused much faster than the pid
+would. Solutions:
+
+a) Introduce the concept of a unique session ID that is never recycled
+   (or not for a long time, anyway). When reading the ProcNumber to
+   send an interrupt to, also read the session ID. When sending the
+   interrupt, check that the session ID matches.
+
+
+TODO: Other
+-----------
+
+- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
+  it's nevertheless more instructions than it used to be.
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
new file mode 100644
index 00000000000..61d148409bc
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,431 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint64 LocalPendingInterrupts;
+
+pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
+
+/*
+ * Install an interrupt handler callback function for the given interrupt.
+ *
+ * You need to also enable the interrupt with EnableInterrupt(), unless you're
+ * replacing an existing handler function.
+ */
+void
+SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler)
+{
+	/*
+	 * XXX: It's somewhat inefficient to loop through all the bits, but this
+	 * isn't performance critical.
+	 */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+		{
+			/* Replace old handler */
+			interrupt_handlers[i] = handler;
+		}
+	}
+}
+
+/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
+void
+EnableInterrupt(InterruptMask interruptMask)
+{
+#ifdef USE_ASSERT_CHECKING
+	/* Check that the interrupt has a handler defined */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
+ *
+ * If an interrupt condition is pending, and it's safe to service it,
+ * then clear the flag and call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning, if it was set when entering.  (This is not the same as
+ * guaranteeing that it's still clear when we return; another interrupt could
+ * have arrived.  But we promise that any pre-existing one will have been
+ * serviced.)
+ */
+void
+ProcessInterrupts(void)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/* Check once what interrupts are pending */
+	interruptsToProcess =
+		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		{
+			/*
+			 * Clear the interrupt *before* calling the handler function, so
+			 * that if the interrupt is received again while the handler
+			 * function is being executed, we won't miss it.
+			 *
+			 * For similar reasons, we also clear the flags one by one even if
+			 * multiple interrupts are pending.  Otherwise if one of the
+			 * interrupt handlers bail out with an ERROR, we would have
+			 * already cleared the other bits, and would miss processing them.
+			 */
+			ClearInterrupt(UINT64_BIT(i));
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint64		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint64		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in some aux processes that
+ * want to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
+
+/* Reserve an interrupt bit for use in an extension */
+InterruptMask
+RequestAddinInterrupt(void)
+{
+	InterruptMask result;
+
+	if (nextAddinInterruptBit == END_ADDIN_INTERRUPTS)
+		elog(ERROR, "out of addin interrupt bits");
+
+	result = UINT64_BIT(nextAddinInterruptBit);
+	nextAddinInterruptBit++;
+	return result;
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..589f755622a
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 *
+	 * The process may ignore the interrupts that these raise, e.g if query
+	 * cancellation is not applicable.  But there's no harm in having the
+	 * signal handlers in place anyway.
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is sent by postmaster to some aux processes, for different
+	 * purposes.  Such processes override this before unblocking signals, but
+	 * ignore it by default.
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/*
+	 * SIGALRM is used for timeouts, but the handler is established later in
+	 * InitializeTimeouts()
+	 */
+	pqsignal(SIGALRM, SIG_IGN);
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 795bfed8d19..314d34a9591 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index df7dc79b827..1a60ca8f78a 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,6 +15,7 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
@@ -421,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.
  *
@@ -457,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
@@ -496,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
@@ -678,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 4da6ac22ff9..3a0ff956823 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -533,8 +534,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 3f9257ab010..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,8 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -174,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -181,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -213,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -255,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -284,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -300,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -307,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -338,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -349,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -364,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 6570f27297b..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,8 +73,8 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
@@ -175,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);
@@ -287,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
@@ -306,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;
 }
@@ -1411,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;
 		}
 
@@ -2062,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 6e4bbfb5aa1..0a029eb02a8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -25,7 +26,6 @@
 
 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);
@@ -75,14 +75,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;
 }
 
@@ -169,27 +168,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
+			SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+						  pq_mq_parallel_leader_proc_number);
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 712a857cdb4..d20e6fba285 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0a730fb5b45..5763057a87a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -27,15 +27,17 @@
  * launcher has set up.
  *
  * If the fork() call fails in the postmaster, it sets a flag in the shared
- * memory area, and sends a signal to the launcher.  The launcher, upon
- * noticing the flag, can try starting the worker again by resending the
+ * memory area, sends a signal to the launcher, raising the
+ * INTERRUPT_AUTOVACUUM_WORKER_FINISHED interrupt. The launcher, upon
+ * noticing the interrupt, can try starting the worker again by resending the
  * signal.  Note that the failure can only be transient (fork failure due to
  * high load, memory pressure, too many processes, etc); more permanent
  * problems, like failure to connect to a database, are detected later in the
  * worker and dealt with just by having the worker exit normally.  The launcher
  * will launch a new worker again later, per schedule.
  *
- * When the worker is done vacuuming it sends SIGUSR2 to the launcher.  The
+ * When the worker is done vacuuming and exits, the postmaster sends SIGUSR2
+ * to the launcher, raising INTERRUPT_AUTOVACUUM_WORKER_FINISHED.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -151,9 +153,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;
@@ -287,6 +286,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
@@ -322,7 +324,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);
@@ -394,21 +396,23 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * Set up signal handlers.  We operate on databases much like a regular
-	 * backend, so we use the same signal handling.  See equivalent code in
-	 * tcop/postgres.c.
+	 * Set up signal and interrupt handlers.  We operate on databases much
+	 * like a regular backend, so we use mostly the same handling.  See
+	 * equivalent code in tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
+	SetStandardInterruptHandlers();
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Postmaster uses SIGUSR2 to raise INTERRUPT_AUTOVACUUM_WORKER_FINISHED */
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
+
+	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -455,7 +459,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();
@@ -497,7 +502,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();
 
 		/*
@@ -556,7 +561,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 */
 	}
@@ -571,41 +576,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -741,21 +749,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? */
@@ -773,17 +777,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();
 }
 
 /*
@@ -1350,8 +1343,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)
@@ -1363,8 +1356,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1399,22 +1391,18 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-
-	/*
-	 * 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, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	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
@@ -1545,12 +1533,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
 	{
@@ -2292,9 +2275,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);
 
 			/*
@@ -2452,7 +2434,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();
 		{
@@ -2541,9 +2523,8 @@ deleted:
 		 * Check for config changes before acquiring lock for further jobs.
 		 */
 		CHECK_FOR_INTERRUPTS();
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			VacuumUpdateCosts();
 		}
@@ -2665,7 +2646,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 8ea800c0bbd..ee16e9cb025 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 133af5aee97..20b7ba737ba 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -79,6 +79,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;
 
 /*
@@ -202,8 +204,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -244,6 +247,19 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	/*
+	 * FIXME: be extra paranoid and sanity check the proc number, since this
+	 * runs in the postmaster
+	 */
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -325,20 +341,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;
@@ -346,8 +362,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;
 		}
@@ -393,23 +409,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;
@@ -431,7 +430,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.
  */
@@ -476,8 +475,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);
 }
 
 /*
@@ -493,12 +492,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
@@ -511,27 +510,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;
+		}
 	}
 }
 
@@ -563,14 +569,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);
 		}
 	}
 }
@@ -624,11 +630,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;
 		}
 	}
 }
@@ -756,32 +757,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, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -987,15 +984,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
@@ -1111,6 +1099,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.
@@ -1211,8 +1218,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)
@@ -1232,9 +1240,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)
 		{
@@ -1242,7 +1250,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1256,8 +1264,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)
@@ -1275,9 +1284,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)
 		{
@@ -1285,7 +1294,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 dbd670c16cc..a53596d49ef 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * Set up interrupt handling
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -220,9 +216,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.
@@ -295,22 +291,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
@@ -325,10 +321,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 978a7d0e649..054872aabce 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -11,8 +11,10 @@
  * condition.)
  *
  * The normal termination sequence is that checkpointer is instructed to
- * execute the shutdown checkpoint by SIGINT.  After that checkpointer waits
- * to be terminated via SIGUSR2, which instructs the checkpointer to exit(0).
+ * execute the shutdown checkpoint by SIGINT, which raises the
+ * INTERRUPT_SHUTDOWN_XLOG interrupt.  After that checkpointer waits
+ * to be terminated via SIGUSR2, raising INTERRUP_TERMINATE, which instructs
+ * the checkpointer to exit(0).
  * All backends must be stopped before SIGINT or SIGUSR2 is issued!
  *
  * Emergency termination is by SIGQUIT; like any backend, the checkpointer
@@ -44,13 +46,14 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -85,10 +88,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
@@ -163,7 +167,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;
@@ -175,7 +178,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);
@@ -205,22 +208,28 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	CheckpointerShmem->checkpointer_pid = MyProcPid;
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * Note: we deliberately ignore SIGTERM, because during a standard Unix
-	 * system shutdown cycle, init will SIGTERM all processes at once.  We
-	 * want to wait for the backends to exit, whereupon the postmaster will
-	 * tell us it's okay to shut down (via SIGUSR2).
+	 * Ignore SIGTERM, because during a standard Unix system shutdown cycle,
+	 * init will SIGTERM all processes at once.  We want to wait for the
+	 * backends to exit, whereupon the postmaster will tell us it's okay to
+	 * shut down (via INTERRUPT_SHUTDOWN_XLOG).
+	 */
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessCheckpointerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
+	/* we will check for INTERRUPT_TERMINATE explicitly in the loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
@@ -359,15 +368,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;
 
 		/*
@@ -541,10 +550,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;
 		}
 
@@ -569,7 +578,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);
@@ -585,10 +594,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);
 	}
 
 	/*
@@ -597,7 +609,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -615,7 +627,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);
 	}
 
 	/*
@@ -625,55 +637,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();
 }
 
 /*
@@ -785,24 +780,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;
 
@@ -817,10 +806,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)
 	{
@@ -832,10 +823,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -924,12 +911,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);
 }
 
 
@@ -1102,14 +1088,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++)
@@ -1128,7 +1115,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1259,7 +1246,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;
@@ -1540,5 +1527,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * Simple signal handler for triggering a configuration reload.
- *
- * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending should be checked at a convenient place within the
- * main loop, or else the main loop should call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForShutdownRequest(SIGNAL_ARGS)
-{
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 10a5ef9e15c..1f4c3576e5b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -132,11 +132,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
  * ----------
@@ -148,10 +143,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -225,18 +221,27 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install an interrupt handler for it?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -247,8 +252,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;
 
@@ -282,13 +287,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);
 }
 
 
@@ -297,8 +301,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);
 }
 
 /*
@@ -309,7 +312,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
@@ -318,13 +321,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -334,7 +339,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);
 
@@ -346,6 +351,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -356,10 +362,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -408,7 +417,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;
 
 			/*
@@ -416,7 +425,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;
@@ -849,30 +858,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 d6133bfebc6..9d9e08c88f9 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -539,10 +539,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +557,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +577,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1644,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1681,20 @@ ServerLoop(void)
 								   0 /* postmaster posts no wait_events */ );
 
 		/*
-		 * Latch set by signal handler, or new connection pending on any of
-		 * our sockets? If the latter, fork a child process to deal with it.
+		 * Interrupt raised by signal handler, or new connection pending on
+		 * any of our sockets?  If the latter, fork a child process to deal
+		 * with it.
 		 */
 		for (int i = 0; i < nevents; i++)
 		{
-			if (events[i].events & WL_LATCH_SET)
-				ResetLatch(MyLatch);
+			if (events[i].events & WL_INTERRUPT)
+				ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/*
 			 * The following requests are handled unconditionally, even if we
-			 * didn't see WL_LATCH_SET.  This gives high priority to shutdown
-			 * and reload requests where the latch happens to appear later in
-			 * events[] or will be reported by a later call to
+			 * didn't see WL_INTERRUPT.  This gives high priority to shutdown
+			 * and reload requests where the interrupt event happens to appear
+			 * later in events[] or will be reported by a later call to
 			 * WaitEventSetWait().
 			 */
 			if (pending_pm_shutdown_request)
@@ -1985,7 +1986,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1996,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2073,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2234,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2581,6 +2582,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2630,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2658,14 @@ CleanupBackend(PMChild *bp,
 	}
 
 	/*
-	 * This backend may have been slated to receive SIGUSR1 when some
-	 * background worker started or stopped.  Cancel those notifications, as
-	 * we don't want to signal PIDs that are not PostgreSQL backends.  This
+	 * This backend may have been slated to receive INTERRUPT_WAIT_WAKEUP when
+	 * some background worker started or stopped.  Cancel those notifications,
+	 * as we don't want to signal PIDs that are not PostgreSQL backends.  This
 	 * gets skipped in the (probably very common) case where the backend has
 	 * never requested any such notifications.
 	 */
 	if (bp_bgworker_notify)
-		BackgroundWorkerStopNotifications(bp_pid);
+		BackgroundWorkerStopNotifications(bp_child_slot);
 
 	/*
 	 * If it was an autovacuum worker, wake up the launcher so that it can
@@ -3921,7 +3924,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4297,15 +4300,15 @@ maybe_start_bgworkers(void)
 		{
 			if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 			{
-				int			notify_pid;
+				ProcNumber	notify_proc_number;
 
-				notify_pid = rw->rw_worker.bgw_notify_pid;
+				notify_proc_number = GetNotifyProcNumberForRegisteredWorker(rw);
 
 				ForgetBackgroundWorker(rw);
 
 				/* Report worker is gone now. */
-				if (notify_pid != 0)
-					kill(notify_pid, SIGUSR1);
+				if (notify_proc_number != INVALID_PROC_NUMBER)
+					SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 330c10ddb0b..bb23642b73f 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * Flags set by interrupt handlers for later service in the redo loop.
- */
-static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t shutdown_requested = false;
-static volatile sig_atomic_t promote_signaled = false;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -77,7 +73,7 @@ int			log_startup_progress_interval = 10000;	/* 10 sec */
 
 /* Signal handlers */
 static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
+static void StartupProcShutdownHandler(SIGNAL_ARGS);
 
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
@@ -92,16 +88,12 @@ static void StartupProcExit(int code, Datum arg);
 static void
 StartupProcTriggerHandler(SIGNAL_ARGS)
 {
-	promote_signaled = true;
-	WakeupRecovery();
-}
-
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-StartupProcSigHupHandler(SIGNAL_ARGS)
-{
-	got_SIGHUP = true;
-	WakeupRecovery();
+	/*
+	 * The INTERRUPT_CHECK_PROMOTE flag hints the startup process to check for
+	 * the signal file, while INTERRUPT_WAL_ARRIVED wakes up the process from
+	 * sleep.
+	 */
+	RaiseInterrupt(INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -111,8 +103,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +122,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +150,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +169,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -225,15 +202,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -241,6 +216,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
 	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
 
+	/* Only set up a subset of the standard interrupt handlers */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessStartupProcTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessStartupProcConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
 	/*
 	 * Unblock signals (they were blocked when the postmaster forked us)
 	 */
@@ -268,7 +250,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -278,18 +260,6 @@ PostRestoreCommand(void)
 	in_restore_command = false;
 }
 
-bool
-IsPromoteSignaled(void)
-{
-	return promote_signaled;
-}
-
-void
-ResetPromoteSignaled(void)
-{
-	promote_signaled = false;
-}
-
 /*
  * Set a flag indicating that it's time to log a progress report.
  */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 8515bfb9653..c9faa76b6a9 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -272,16 +272,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -322,7 +320,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
@@ -332,9 +330,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 */
@@ -350,14 +351,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);
 
 			/*
@@ -1182,7 +1182,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);
 	}
@@ -1193,8 +1193,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();
@@ -1585,9 +1585,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 4e4eed4d076..d8c754ad280 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -31,17 +31,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -146,7 +146,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);
@@ -240,16 +240,15 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 			(errmsg_internal("WAL summarizer started")));
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -309,10 +308,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) */
@@ -349,7 +346,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();
@@ -624,8 +621,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)
@@ -640,7 +637,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -849,27 +846,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();
 }
 
 /*
@@ -1008,7 +995,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);
@@ -1495,7 +1482,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1533,7 +1520,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. */
@@ -1633,11 +1620,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1684,7 +1675,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,
@@ -1703,7 +1694,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 1851417ad1b..93dfc98cc34 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,14 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -209,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))
 		{
@@ -222,11 +221,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -250,9 +248,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 1730ace5490..bfdb07a3b79 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,10 +157,12 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
@@ -238,12 +240,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -706,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)
@@ -759,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);
@@ -805,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);
 			}
 		}
 		else
@@ -844,9 +831,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -871,15 +857,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();
 
 	/*
@@ -940,8 +929,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;
@@ -975,29 +963,7 @@ ParallelApplyWorkerMain(Datum main_arg)
 	set_apply_error_context_origin(originname);
 
 	LogicalParallelApplyLoop(mqh);
-
-	/*
-	 * The parallel apply worker must not get here because the parallel apply
-	 * worker will only stop when it receives a SIGTERM or SIGUSR2 from the
-	 * leader, or SIGINT from itself, or when there is an error. None of these
-	 * cases will allow the code to reach here.
-	 */
-	Assert(false);
-}
-
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	Assert(false);				/* LogicalParallelApplyLoop never returns */
 }
 
 /*
@@ -1064,7 +1030,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1074,6 +1040,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1097,8 +1066,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1189,14 +1156,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();
 		}
 
@@ -1261,13 +1229,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 e6112e11ec2..d66ba0b6f5a 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -58,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;
@@ -183,7 +184,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -219,27 +219,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -473,6 +474,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
@@ -539,7 +541,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))
@@ -566,7 +567,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -589,13 +590,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -616,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -630,13 +632,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -663,7 +666,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);
@@ -672,8 +675,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -710,13 +714,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
@@ -738,7 +742,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.
  */
@@ -747,7 +751,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -815,7 +819,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);
@@ -847,6 +851,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;
 }
 
@@ -858,7 +863,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;
 }
 
 /*
@@ -1019,7 +1024,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);
@@ -1045,6 +1049,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1193,8 +1198,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);
 }
 
 /*
@@ -1208,13 +1217,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).
@@ -1256,6 +1268,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)
 		{
@@ -1416,21 +1429,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);
 		}
 	}
 
@@ -1585,7 +1596,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 165f909b3ba..92c903d7f96 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 5e1ee7e4728..9dbf89cb832 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,9 +59,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -79,9 +80,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -104,7 +105,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1223,7 +1224,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;
@@ -1286,13 +1287,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1314,7 +1316,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1358,7 +1362,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
@@ -1404,13 +1408,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1418,7 +1425,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);
 
@@ -1430,16 +1437,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);
 
@@ -1454,7 +1461,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;
@@ -1529,19 +1536,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1681,7 +1684,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);
 
@@ -1720,7 +1723,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1737,7 +1740,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1745,8 +1748,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1754,13 +1757,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1845,7 +1849,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1923,7 +1927,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 19a3c21a863..1cef9a499e6 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -167,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;
@@ -218,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;
@@ -518,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.
 						 */
@@ -697,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 75af3a71ca8..29ad2ac4f92 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -4053,11 +4054,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();
@@ -4176,11 +4174,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4201,23 +4199,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5888,8 +5885,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 28c7019402b..9742ddd1fd4 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -622,7 +622,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	int			active_pid;
 
 	Assert(name != NULL);
 
@@ -685,7 +684,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -707,7 +705,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -1968,7 +1966,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1978,7 +1976,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		int			active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2062,11 +2059,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2105,22 +2097,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2161,7 +2156,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3253,11 +3249,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))
@@ -3267,6 +3260,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a way to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index 7ea6001e9ad..35ec8047391 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -265,22 +266,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -294,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))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -314,9 +315,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.")));
@@ -325,11 +325,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,
@@ -337,7 +341,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -944,7 +948,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 352a3f31e95..f0698e61194 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -246,16 +247,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
-	/* Properly accept or ignore signals the postmaster might send us */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -439,9 +432,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();
@@ -514,25 +506,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -680,7 +674,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -712,8 +706,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)
@@ -1384,7 +1382,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index 42e3e170bc0..56ca8f7389c 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"
@@ -332,7 +333,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 5acc8ee3ac8..446eafe694f 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -201,15 +202,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_STOPPING = false;
-
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM.
+ * When set, the main loop is responsible for checking
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -282,7 +280,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -370,7 +368,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -738,8 +737,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();
@@ -773,7 +780,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)
@@ -972,12 +981,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1036,9 +1047,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1475,7 +1486,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1529,7 +1540,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1619,12 +1630,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();
 
@@ -1664,23 +1671,27 @@ ProcessPendingWrites(void)
 
 		/* Sleep until something happens or we time out */
 		WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime,
-				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
+				   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+				   CheckForInterruptsMask |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1783,7 +1794,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1870,12 +1881,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();
@@ -1885,7 +1896,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			XLogBackgroundFlush();
 
 		/*
@@ -1910,7 +1921,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1992,11 +2003,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2022,7 +2037,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2895,6 +2910,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2909,13 +2925,20 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
 
-		CHECK_FOR_INTERRUPTS();
+		/* Clear any already-pending wakeups */
+		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();
@@ -2964,13 +2987,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);
 		}
 
@@ -3022,7 +3045,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3060,6 +3088,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3116,6 +3145,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3210,7 +3240,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3579,8 +3609,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3697,52 +3727,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3825,24 +3846,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SetLatch(), helping
-	 * walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3890,16 +3915,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 d2c9cd6f20a..64df384656b 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d9617c20e76..80eadefcd1d 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,22 +262,24 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
-			pgaio_debug_io(DEBUG4, staged_ios[i],
+			pgaio_debug_io(LOG, staged_ios[i],
 						   "choosing worker %d",
 						   worker);
 		}
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -392,20 +393,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -453,11 +454,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -490,13 +491,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -567,18 +568,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d1babaff023..cb61e6e1fce 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3345,7 +3346,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);
@@ -3525,7 +3526,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3606,7 +3607,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3697,7 +3698,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);
 	}
@@ -6643,12 +6644,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other signals as well. We
+		 * take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e208457df27..367c1168216 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -357,8 +357,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	/*
 	 * Block all blockable signals, except SIGQUIT.  posix_fallocate() can run
 	 * for quite a long time, and is an all-or-nothing operation.  If we
-	 * allowed SIGUSR1 to interrupt us repeatedly (for example, due to
-	 * recovery conflicts), the retry loop might never succeed.
+	 * allowed signals to interrupt us repeatedly (for example, due to config
+	 * file reloads), the retry loop might never succeed.
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..806947571ae 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -172,13 +173,12 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests; we're doing our best to
-	 * close up shop already.  Note that the signal handlers will not set
-	 * these flags again, now that proc_exit_inprogress is set.
+	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
+	 * to prevent any more interrupts from being processed; we're doing our
+	 * best to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 40312df2cac..b34e2cb731e 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"
@@ -3450,27 +3451,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3510,10 +3511,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;
 		}
@@ -3545,21 +3546,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 5d33559926a..b80426828b1 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -26,7 +27,7 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -34,28 +35,21 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
  * (or greater) appears in the pss_barrierGeneration flag of every process,
  * we know that the message has been received everywhere.
@@ -65,7 +59,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) */
@@ -105,7 +98,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -150,7 +142,6 @@ ProcSignalShmemInit(void)
 			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);
@@ -181,9 +172,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -232,9 +220,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;
@@ -269,73 +258,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
@@ -380,8 +302,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
@@ -392,25 +314,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;
@@ -470,23 +379,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.
  *
@@ -502,12 +394,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
@@ -639,68 +527,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -759,6 +586,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 3ce6068ac54..ab995093140 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * process interrupts are used for synchronization.  Only the sender may send,
  * and only the receiver may receive.  This is intended to allow a user
  * backend to communicate with worker backends that it has registered.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -44,10 +45,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.
@@ -112,7 +114,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
@@ -214,7 +216,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));
 }
 
 /*
@@ -232,7 +234,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));
 }
 
 /*
@@ -341,16 +343,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
@@ -539,7 +540,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;
 	}
 
@@ -557,16 +558,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)
@@ -619,8 +619,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)
 	{
@@ -895,7 +895,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +993,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
@@ -1001,7 +1001,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;
@@ -1009,17 +1009,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();
@@ -1054,7 +1055,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.
 			 */
@@ -1150,25 +1151,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);
 	}
 }
 
@@ -1250,14 +1255,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;
@@ -1293,7 +1302,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 d48b4fe3799..aaeebca6c91 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -41,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
@@ -201,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..dcabd25956a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and will set the
- * processes latch. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * setting the process's latch.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,15 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
-		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
-		 * can just call AcceptInvalidationMessages() to do this.  If we
-		 * aren't, we start and immediately end a transaction; the call to
-		 * AcceptInvalidationMessages() happens down inside transaction start.
+		 * run, which will do the necessary work.  If we are inside a
+		 * transaction we can just call AcceptInvalidationMessages() to do
+		 * this.  If we aren't, we start and immediately end a transaction;
+		 * the call to AcceptInvalidationMessages() happens down inside
+		 * transaction start.
 		 *
 		 * It is awfully tempting to just call AcceptInvalidationMessages()
 		 * without the rest of the xact start/stop overhead, and I think that
@@ -190,14 +148,20 @@ ProcessCatchupInterrupt(void)
 		 */
 		if (IsTransactionOrTransactionBlock())
 		{
-			elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt inside transaction");
 			AcceptInvalidationMessages();
 		}
 		else
 		{
-			elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt outside transaction");
 			StartTransactionCommand();
 			CommitTransactionCommand();
 		}
+
+		/*
+		 * If another catchup interrupt arrived while we were procesing the
+		 * previous one, process the new one too.
+		 */
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -658,8 +666,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -670,8 +678,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index d83afbfb9d6..c9242d6fb89 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -596,7 +597,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
@@ -698,7 +699,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
@@ -746,7 +751,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:
@@ -766,15 +775,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
@@ -838,9 +848,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);
@@ -936,6 +956,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -945,6 +966,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -954,6 +976,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 772e350a0c0..612de8b35e9 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -128,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,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -183,9 +184,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
 
@@ -235,7 +243,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)
@@ -285,12 +293,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");
@@ -311,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -349,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -412,7 +426,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;
 
@@ -496,9 +511,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)
 		{
@@ -535,7 +550,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
@@ -553,10 +568,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.
@@ -566,7 +577,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, InterruptMask interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +591,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 */
@@ -608,10 +616,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)
@@ -645,14 +653,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)
@@ -682,40 +690,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +747,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)
@@ -794,9 +795,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)
@@ -858,9 +858,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;
@@ -886,8 +886,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 |
@@ -902,10 +901,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
 	{
@@ -985,10 +984,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)
 	{
@@ -1044,6 +1046,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);
 
@@ -1065,100 +1068,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		InterruptMask old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
+		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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1229,16 +1249,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1391,13 +1411,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1513,16 +1533,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1621,6 +1641,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
@@ -1726,19 +1755,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1783,7 +1808,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
@@ -1889,18 +1914,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)
 {
@@ -1917,7 +1941,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;
@@ -2004,7 +2028,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2014,12 +2037,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();
@@ -2027,12 +2049,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..f961054a4ba 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1584,7 +1585,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;
@@ -3617,7 +3622,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 ba7eaed1a13..44809fea210 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -214,6 +215,8 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -301,14 +304,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -518,13 +535,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);
@@ -688,13 +700,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -717,6 +724,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.
@@ -986,21 +997,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1059,7 +1069,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 */
@@ -1078,11 +1088,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);
 
@@ -1410,18 +1429,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
 	{
@@ -1467,9 +1486,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)
 			{
@@ -1675,8 +1695,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)
 	{
@@ -1713,7 +1733,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 links invalid.
  *
@@ -1742,7 +1762,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1899,14 +1919,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;
 }
 
@@ -1985,34 +2003,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 378c2a03f39..dd8b1fc10f5 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 b1accc68b95..fbed7d82e98 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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 c517115927c..9595476c8e0 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -198,6 +198,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
 
+	/*
+	 * FIXME: install INTERRUPT_TERMINATE handler at least? Not really needed
+	 * because no one else can send us interrupts yet, and we don't raise them
+	 * within the backend yet either. But if we changed postmaster to send
+	 * INTERRUPT_TERMINATE directly, for example, we'd need to handle it
+	 * already.
+	 */
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index b88ce440b1d..bb022270c75 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -335,8 +336,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -353,11 +352,23 @@ SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
+
+	/*
+	 * Don't allow query cancel interrupts while reading input from the
+	 * client, because we might lose sync in the FE/BE protocol.  (Die
+	 * interrupts are OK, because we won't read any further messages from the
+	 * client in that case.)
+	 *
+	 * See similar logic in ProcessRecoveryConflictInterrupt().
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Get message type code from the frontend.
 	 */
-	HOLD_CANCEL_INTERRUPTS();
 	pq_startmsgread();
 	qtype = pq_getbyte();
 
@@ -464,7 +475,9 @@ SocketBackend(StringInfo inBuf)
 	 */
 	if (pq_getmessage(inBuf, maxmsglen))
 		return EOF;				/* suitable message already logged */
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	return qtype;
 }
@@ -488,104 +501,6 @@ ReadCommand(StringInfo inBuf)
 	return result;
 }
 
-/*
- * ProcessClientReadInterrupt() - Process interrupts specific to client reads
- *
- * This is called just before and after low-level reads.
- * 'blocked' is true if no data was available to read and we plan to retry,
- * false if about to read or done reading.
- *
- * Must preserve errno!
- */
-void
-ProcessClientReadInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (DoingCommandRead)
-	{
-		/* Check for general interrupts that arrived before/while reading */
-		CHECK_FOR_INTERRUPTS();
-
-		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
-			ProcessCatchupInterrupt();
-
-		/* Process notify interrupts, if any */
-		if (notifyInterruptPending)
-			ProcessNotifyInterrupt(true);
-	}
-	else if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If there is no data available to read, then it's safe
-		 * (and sane) to handle that now.  If we haven't tried to read yet,
-		 * make sure the process latch is set, so that if there is no data
-		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while reading.
-		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
-/*
- * ProcessClientWriteInterrupt() - Process interrupts specific to client writes
- *
- * This is called just before and after low-level writes.
- * 'blocked' is true if no data could be written and we plan to retry,
- * false if about to write or done writing.
- *
- * Must preserve errno!
- */
-void
-ProcessClientWriteInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If it's not possible to write, then we should handle
-		 * that immediately, else a stuck client could indefinitely delay our
-		 * response to the signal.  If we haven't tried to write yet, make
-		 * sure the process latch is set, so that if the write would block
-		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while writing.
-		 */
-		if (blocked)
-		{
-			/*
-			 * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't
-			 * service ProcDiePending.
-			 */
-			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-			{
-				/*
-				 * We don't want to send the client the error message, as a)
-				 * that would possibly block again, and b) it would likely
-				 * lead to loss of protocol sync because we may have already
-				 * sent a partial protocol message.
-				 */
-				if (whereToSendOutput == DestRemote)
-					whereToSendOutput = DestNone;
-
-				CHECK_FOR_INTERRUPTS();
-			}
-		}
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
 /*
  * Do raw parsing (only).
  *
@@ -2910,7 +2825,7 @@ drop_unnamed_stmt(void)
  * Either some backend has bought the farm, or we've been told to shut down
  * "immediately"; so we need to stop what we're doing and exit.
  */
-void
+static void
 quickdie(SIGNAL_ARGS)
 {
 	sigaddset(&BlockSig, SIGQUIT);	/* prevent nested calls */
@@ -3007,50 +2922,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-void
-StatementCancelHandler(SIGNAL_ARGS)
-{
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3067,22 +2958,91 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
- * in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	/*
+	 * Check the conflicts one by one, clearing each flag only before
+	 * processing the particular conflict.  This ensures that if multiple
+	 * conflicts are pending, we come back here to process the remaining
+	 * conflicts, if an error is thrown during processing one of them.
+	 */
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
+		 reason++)
+	{
+		if ((pending & (1 << reason)) != 0)
+		{
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
+			ProcessRecoveryConflict(reason);
+		}
+	}
+}
+
+void
+ProcessRecoveryConflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
@@ -3242,14 +3202,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,72 +3240,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
-
-	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
-	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * Check the conflicts one by one, clearing each flag only before
-	 * processing the particular conflict.  This ensures that if multiple
-	 * conflicts are pending, we come back here to process the remaining
-	 * conflicts, if an error is thrown during processing one of them.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
- *
- * If an interrupt condition is pending, and it's safe to service it,
- * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
- *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts
- * is guaranteed to clear the InterruptPending flag before returning.
- * (This is not the same as guaranteeing that it's still clear when we
- * return; another interrupt could have arrived.  But we promise that
- * any pre-existing one will have been serviced.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3394,65 +3313,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		CheckClientConnectionPending = false;
-
-		/*
-		 * Check for lost connection and re-arm, if still configured, but not
-		 * if we've arrived back at DoingCommandRead state.  We don't want to
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * Check for lost connection and re-arm, if still configured, but not if
+	 * we've arrived back at DoingCommandRead state.  We don't want to wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
+
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
 
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
+
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
-	{
-		/*
-		 * Re-arm InterruptPending so that we process the cancel request as
-		 * soon as we're done reading the message.  (XXX this is seriously
-		 * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we
-		 * can't use that macro directly as the initial test in this function,
-		 * meaning that this code also creates opportunities for other bugs to
-		 * appear.)
-		 */
-		InterruptPending = true;
-	}
-	else if (QueryCancelPending)
+
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3505,76 +3414,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * interrupt.  We need to unset the flag before the injection point,
-		 * otherwise we could loop in interrupts checking.
-		 */
-		IdleInTransactionSessionTimeoutPending = false;
-		if (IdleInTransactionSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4238,9 +4137,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4253,13 +4156,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4273,7 +4188,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4282,11 +4204,31 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4454,11 +4396,14 @@ PostgresMain(const char *dbname, const char *username)
 		 * forgetting a timeout cancel.
 		 */
 		disable_all_timeouts(false);	/* do first to avoid race condition */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 		idle_in_transaction_timeout_enabled = false;
 		idle_session_timeout_enabled = false;
 
 		/* Not reading from the client anymore. */
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 		DoingCommandRead = false;
 
 		/* Make sure libpq is in a good state */
@@ -4639,7 +4584,7 @@ PostgresMain(const char *dbname, const char *username)
 				 * were received during the just-finished transaction, they'll
 				 * be seen by the client before ReadyForQuery is.
 				 */
-				if (notifyInterruptPending)
+				if (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4728,6 +4673,28 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		DoingCommandRead = true;
 
+		/*
+		 * Let cache inval catchup requests to be processed while we're idle.
+		 */
+		EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+
+		/*
+		 * When we're truly idle, i.e. no transaction in progress, we can
+		 * additionally process any async LISTEN/NOTIFY messages and stats
+		 * update requests.
+		 */
+		if (!IsTransactionOrTransactionBlock())
+		{
+			EnableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+			EnableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+		}
+
+		/*
+		 * If any of the newly-enabled interrupts are already pending, process
+		 * them now.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * (3) read a command (loop blocks here)
 		 */
@@ -4757,22 +4724,22 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 87070235d11..6aa69805891 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -295,14 +295,7 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 	}
 
-	procNumber = GetNumberFromPGProc(proc);
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
+	SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc));
 
 	PG_RETURN_BOOL(true);
 }
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 32a787d7df7..9f660363921 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,16 +346,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;
 
@@ -374,11 +374,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 8deb2369471..df7657031b2 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1739,10 +1739,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 cb1c9d85ffe..becfc62fd32 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -527,7 +527,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,6 @@
 
 ProtocolVersion FrontendProtocol;
 
-volatile sig_atomic_t InterruptPending = false;
-volatile sig_atomic_t QueryCancelPending = false;
-volatile sig_atomic_t ProcDiePending = false;
-volatile sig_atomic_t CheckClientConnectionPending = false;
-volatile sig_atomic_t ClientConnectionLost = false;
-volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t TransactionTimeoutPending = false;
-volatile sig_atomic_t IdleSessionTimeoutPending = false;
-volatile sig_atomic_t ProcSignalBarrierPending = false;
-volatile sig_atomic_t LogMemoryContextPending = false;
-volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 QueryCancelHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
 
 int			MyProcPid;
 pg_time_t	MyStartTime;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fadf856d9bc..fdcee946405 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -143,25 +139,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -201,10 +184,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.
@@ -225,48 +207,6 @@ InitStandaloneProcess(const char *argv0)
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
 
-void
-SwitchToSharedLatch(void)
-{
-	Assert(MyLatch == &LocalLatchData);
-	Assert(MyProc != NULL);
-
-	MyLatch = &MyProc->procLatch;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	/*
-	 * Set the shared latch as the local one might have been set. This
-	 * shouldn't normally be necessary as code is supposed to check the
-	 * condition before waiting for the latch, but a bit care can't hurt.
-	 */
-	SetLatch(MyLatch);
-}
-
-void
-InitProcessLocalLatch(void)
-{
-	MyLatch = &LocalLatchData;
-	InitLatch(MyLatch);
-}
-
-void
-SwitchBackToLocalLatch(void)
-{
-	Assert(MyLatch != &LocalLatchData);
-	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);
-
-	MyLatch = &LocalLatchData;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	SetLatch(MyLatch);
-}
-
 /*
  * Return a human-readable string representation of a BackendType.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..4f1aa94a661 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -1371,20 +1372,19 @@ ShutdownPostgres(int code, Datum arg)
 static void
 StatementTimeoutHandler(void)
 {
-	int			sig = SIGINT;
-
 	/*
 	 * During authentication the timeout is used to deal with
 	 * authentication_timeout - we want to quit in response to such timeouts.
 	 */
 	if (ClientAuthInProgress)
-		sig = SIGTERM;
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+	else
+		RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, sig);
-#endif
-	kill(MyProcPid, sig);
+	/*
+	 * FIXME: we used to signal the whole process group. Is that important, if
+	 * we're e.g. executing archive_command ?
+	 */
 }
 
 /*
@@ -1393,51 +1393,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
- * to call this function if we see LogMemoryContextPending set.
- * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
- * the target process for logging of memory contexts is a backend.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/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 01bdf2bec1f..21b0b0a1ec7 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -53,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;
 
@@ -70,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..239d58f7597
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,370 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ */
+#include "storage/waiteventset.h"
+
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 63
+
+/*
+ * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+ * waiting for an interrupt.  If set, the backend needs to be woken up when a
+ * bit in the pending interrupts mask is set.  It's used internally by the
+ * interrupt machinery, and cannot be used directly in the public functions.
+ * It's named differently to distinguish it from the actual interrupt flags.
+ */
+#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
+
+extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 412bc9758fb..35fbff9a763 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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 c62fffb5998..3ba2b82b3a8 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -17,7 +17,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/meson.build b/src/include/meson.build
index 7d734d92dab..da8129355cc 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -135,6 +135,7 @@ header_subdirs = [
   'executor',
   'fe_utils',
   'foreign',
+  'ipc',
   'jit',
   'lib',
   'libpq',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..476e60a4d06 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,132 +28,15 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
-
-#define InvalidPid				(-1)
-
-
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/interrupt.h"
 #endif
 
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
+#define InvalidPid				(-1)
 
 
 /*****************************************************************************
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
-
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TableSyncWorkerMain(Datum main_arg);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if
+								 * not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index c1285fdd1bc..4b6449e974a 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index fbdadc86959..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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 */
-
-/*
- * 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 1f1ab7d02ca..00ea9f86422 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -18,7 +18,6 @@
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -190,9 +189,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -236,16 +232,6 @@ struct PGPROC
 
 	BackendType backendType;	/* what kind of process is this? */
 
-	/*
-	 * While in hot standby mode, shows that a conflict signal has been sent
-	 * for the current transaction. Set/cleared while holding ProcArrayLock,
-	 * though not required. Accessed without lock, if needed.
-	 *
-	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
-	 * enum value.
-	 */
-	pg_atomic_uint32 pendingRecoveryConflicts;
-
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
@@ -337,6 +323,24 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint64 pendingInterrupts;
+
+	/*
+	 * While in hot standby mode, shows that a conflict signal has been sent
+	 * for the current transaction. Set/cleared while holding ProcArrayLock,
+	 * though not required. Accessed without lock, if needed.
+	 *
+	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
+	 * enum value.
+	 */
+	pg_atomic_uint32 pendingRecoveryConflicts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -451,6 +455,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;
@@ -533,9 +539,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..a7f974c5ba2 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,17 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +73,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
+
+struct ResourceOwnerData;
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  struct Latch *latch, void *user_data);
+							  uint64 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint64 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 11ab1717a16..5d47643eb77 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 4c31e503eb1..0bd50bdb23f 100644
--- a/src/port/pgsleep.c
+++ b/src/port/pgsleep.c
@@ -32,10 +32,10 @@
  *
  * CAUTION: It's not a good idea to use long sleeps in the backend.  They will
  * silently return early if a signal is caught, but that doesn't include
- * latches being set on most OSes, and even signal handlers that set MyLatch
- * might happen to run before the sleep begins, allowing the full delay.
- * Better practice is to use WaitLatch() with a timeout, so that backends
- * respond to latches and signals promptly.
+ * interrupts being received on most OSes, and even signal handlers that raise
+ * an interrupt might happen to run before the sleep begins, allowing the full
+ * delay.  Better practice is to use WaitInterrupt() with a timeout, so that
+ * backends respond to interrupts and signals promptly.
  */
 void
 pg_usleep(long microsec)
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index a0aec04d994..777d00283da 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 579e5933d28..e6169f97a9e 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -133,6 +134,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;
@@ -222,8 +224,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)
@@ -264,6 +264,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;
@@ -286,11 +289,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 fe1794c6077..d8675ac1423 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "varatt.h"
 
@@ -170,6 +170,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
@@ -234,13 +236,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 368e4f3f234..c75b5edaafa 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,7 +19,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -52,15 +52,12 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/*
-	 * Establish signal handlers.
-	 *
-	 * We want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
-	 * it would a normal user backend.  To make that happen, we use die().
+	 * The standard interrupt and signal handlers are OK for us, in particular
+	 * we want CHECK_FOR_INTERRUPTS() to kill off this worker process just as
+	 * it would a normal user backend. Just unblock signals.
 	 */
-	pqsignal(SIGTERM, die);
 	BackgroundWorkerUnblockSignals();
 
 	/*
@@ -122,13 +119,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 d1e4a2bd952..8f9fa9e1834 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -155,11 +153,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -213,26 +210,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
@@ -366,7 +362,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.
@@ -420,8 +415,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 241945734ec..9a5d8ac10e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1564,7 +1565,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2028,6 +2028,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.47.3



  [text/x-patch] v10-0005-Introduce-PendingInterrupts-struct-with-separate.patch (24.2K, ../../[email protected]/6-v10-0005-Introduce-PendingInterrupts-struct-with-separate.patch)
  download | inline diff:
From 6e5b9fcd7049314a113961447cc3d1545e19133e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 18 Feb 2026 00:55:34 +0200
Subject: [PATCH v10 5/5] Introduce PendingInterrupts struct with separate
 'flags' field

This does a couple of things:

- Split the interrupts mask into two 32-bit fields on
  PG_HAVE_ATOMIC_U64_SIMULATION systems. This fixes the self-deadlock
  risk when used from signal handlers.

- Instead of stealing bits from the interrupts mask itself, introduce
  a new 'flags' field for the SLEEPING_ON_INTERRUPTS flag, and for the
  new CFI_ATTENTION flag (see next bullet). One reason for this is
  that it makes the PG_HAVE_ATOMIC_U64_SIMULATION codepaths less
  special, since we now need to be careful about the order that the
  flag and the interrupt bits are manipulated, even with 64-bit
  atomics. (It's not nice that we have to be careful, of course, but
  it's nice that the codepaths don't differ so much).

- Introduce a new CFI_ATTENTION flag. It is set whenever any of the
  interrupt bits are set. This allows CHECK_FOR_INTERRUPTS() to be
  smaller, as it can just check the CFI_ATTENTION flag and fall
  through very quickly in the common case. ProcessInterrupts() and all
  the places where CheckForInterruptsMask is changed need to clear and
  recheck the flag. This makes CHECK_FOR_INTERRUPTS() smaller and
  faster, at the cost of making SendInterrupt more complicated, and
  having CHECK_FOR_INTERRUPTS() call ProcessInterrupts() unnecessarily
  when disabled interrupts are set, but that seems like a good
  tradeoff.
---
 src/backend/ipc/interrupt.c            | 183 +++++++++++++++++--------
 src/backend/storage/ipc/waiteventset.c |  37 +++--
 src/include/ipc/interrupt.h            | 136 +++++++++++++++---
 src/include/storage/proc.h             |   2 +-
 src/tools/pgindent/typedefs.list       |   1 +
 5 files changed, 271 insertions(+), 88 deletions(-)

diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
index 61d148409bc..2f39cdf4aff 100644
--- a/src/backend/ipc/interrupt.c
+++ b/src/backend/ipc/interrupt.c
@@ -53,9 +53,9 @@ static WaitEventSet *InterruptWaitSet;
 #define InterruptWaitSetInterruptPos 0
 #define InterruptWaitSetPostmasterDeathPos 1
 
-static pg_atomic_uint64 LocalPendingInterrupts;
+static PendingInterrupts LocalPendingInterrupts;
 
-pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+PendingInterrupts *MyPendingInterrupts = &LocalPendingInterrupts;
 
 static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
 
@@ -94,10 +94,12 @@ EnableInterrupt(InterruptMask interruptMask)
 			Assert(interrupt_handlers[i] != NULL);
 	}
 #endif
-
 	EnabledInterruptsMask |= interruptMask;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+	{
 		CheckForInterruptsMask = EnabledInterruptsMask;
+		RECHECK_CFI_ATTENTION();
+	}
 }
 
 /*
@@ -113,7 +115,10 @@ DisableInterrupt(InterruptMask interruptMask)
 {
 	EnabledInterruptsMask &= ~interruptMask;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+	{
 		CheckForInterruptsMask = EnabledInterruptsMask;
+		RECHECK_CFI_ATTENTION();
+	}
 }
 
 /*
@@ -132,47 +137,79 @@ DisableInterrupt(InterruptMask interruptMask)
 void
 ProcessInterrupts(void)
 {
+	uint64		pending;
 	InterruptMask interruptsToProcess;
 
-	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+	/*
+	 * This shouldn't be called while sleeping. CHECK_FOR_INTERRUPTS() relies
+	 * on there being no CHECK_FOR_INTERRUPTS() calls in the code that runs
+	 * while PI_FLAG_SLEEPING_ON_INTERRUPTS is set. That assumption allows
+	 * CHECK_FOR_INTERRUPTS() to check "MyPendingInterrupts->flags == 0",
+	 * which is slightly less expensive than "(MyPendingInterrupts->flags &
+	 * PI_FLAG_CFI_ATTENTION) == 0".
+	 */
+	Assert((pg_atomic_read_u32(&MyPendingInterrupts->flags) &
+			PI_FLAG_SLEEPING_ON_INTERRUPTS) == 0);
 
 	/* Check once what interrupts are pending */
-	interruptsToProcess =
-		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_hi) << 32;
+#endif
+	interruptsToProcess = pending & CheckForInterruptsMask;
 
-	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	if (interruptsToProcess != 0)
 	{
-		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+		for (int i = 0; i < lengthof(interrupt_handlers); i++)
 		{
-			/*
-			 * 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]) ();
+			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]) ();
+			}
 		}
 	}
+
+	/*
+	 * We can clear the CFI_ATTENTION flag now (unless new interrupts arrived
+	 * while we were processing).
+	 */
+	RECHECK_CFI_ATTENTION();
 }
 
+
 /*
- * Switch to local interrupts.  Other backends can't send interrupts to this
- * one.  Only RaiseInterrupt() can set them, from inside this process.
+ * Move all the bits from *src to *dst, clearing all the bits in *dst.
  */
-void
-SwitchToLocalInterrupts(void)
+static void
+SwitchMyPendingInterruptsPtr(PendingInterrupts *new_ptr)
 {
-	if (MyPendingInterrupts == &LocalPendingInterrupts)
+	PendingInterrupts *old_ptr = MyPendingInterrupts;
+
+	/* should not be called while sleeping */
+	Assert((pg_atomic_read_u32(&MyPendingInterrupts->flags) & PI_FLAG_SLEEPING_ON_INTERRUPTS) == 0);
+
+	if (new_ptr == old_ptr)
 		return;
 
-	MyPendingInterrupts = &LocalPendingInterrupts;
+	MyPendingInterrupts = new_ptr;
 
 	/*
 	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
@@ -181,14 +218,42 @@ SwitchToLocalInterrupts(void)
 	pg_memory_barrier();
 
 	/*
-	 * Mix in the interrupts that we have received already in our shared
-	 * interrupt vector, while atomically clearing it.  Other backends may
-	 * continue to set bits in it after this point, but we've atomically
+	 * 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.
 	 */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	{
+		uint64		old_interrupts;
+
+		old_interrupts = pg_atomic_exchange_u64(&old_ptr->interrupts, 0);
+		pg_atomic_fetch_or_u64(&new_ptr->interrupts, old_interrupts);
+	}
+#else
+	{
+		uint32		old_interrupts_lo;
+		uint32		old_interrupts_hi;
+
+		old_interrupts_lo = pg_atomic_exchange_u32(&old_ptr->interrupts_lo, 0);
+		old_interrupts_hi = pg_atomic_exchange_u32(&old_ptr->interrupts_hi, 0);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_lo, old_interrupts_lo);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_hi, old_interrupts_hi);
+	}
+#endif
+
+	RECHECK_CFI_ATTENTION();
+}
+
+/*
+ * 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);
 }
 
 /*
@@ -198,20 +263,40 @@ SwitchToLocalInterrupts(void)
 void
 SwitchToSharedInterrupts(void)
 {
-	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
-		return;
+	SwitchMyPendingInterruptsPtr(&MyProc->pendingInterrupts);
+}
+
+static bool
+SendOrRaiseInterrupt(PendingInterrupts *ptr, InterruptMask interruptMask)
+{
+	uint64		old_pending;
+	uint32		old_flags;
+
+	/* Set the given bits, and atomically read the old ones */
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	old_pending = pg_atomic_fetch_or_u64(&ptr->interrupts, interruptMask);
+#else
+	old_pending = (uint64) pg_atomic_fetch_or_u32(&ptr->interrupts_lo, (uint32) interruptMask);
+	old_pending |= (uint64) pg_atomic_fetch_or_u32(&ptr->interrupts_hi,(uint32) (interruptMask >> 32)) << 32;
+#endif
 
-	MyPendingInterrupts = &MyProc->pendingInterrupts;
+	if ((old_pending & interruptMask) == interruptMask)
+		return false;			/* no new bits were set */
 
 	/*
-	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
-	 * seeing the new MyPendingInterrupts destination.
+	 * We set some bits. Set the CFI_ATTENTION flag too, so that the
+	 * CHECK_FOR_INTERRUPTS() call knows to check for the interrupt.
 	 */
-	pg_memory_barrier();
+	old_flags = pg_atomic_fetch_or_u32(&ptr->flags, PI_FLAG_CFI_ATTENTION);
 
-	/* Mix in any unhandled bits from LocalPendingInterrupts. */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+	/*
+	 * Furthermore, if the process is currently blocked waiting for an
+	 * interrupt to arrive, wake it up.
+	 */
+	if ((old_flags & PI_FLAG_SLEEPING_ON_INTERRUPTS) != 0)
+		return true;
+	else
+		return false;
 }
 
 /*
@@ -223,15 +308,7 @@ SwitchToSharedInterrupts(void)
 void
 RaiseInterrupt(InterruptMask interruptMask)
 {
-	uint64		old_pending;
-
-	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
-
-	/*
-	 * If the process is currently blocked waiting for an interrupt to arrive,
-	 * and the interrupt wasn't already pending, wake it up.
-	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(MyPendingInterrupts, interruptMask))
 		WakeupMyProc();
 }
 
@@ -248,14 +325,12 @@ void
 SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 {
 	PGPROC	   *proc;
-	uint64		old_pending;
 
 	Assert(pgprocno != INVALID_PROC_NUMBER);
 	Assert(pgprocno >= 0);
 	Assert(pgprocno < ProcGlobal->allProcCount);
 
 	proc = &ProcGlobal->allProcs[pgprocno];
-	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
 
 	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
 
@@ -263,7 +338,7 @@ SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 	 * If the process is currently blocked waiting for an interrupt to arrive,
 	 * and the interrupt wasn't already pending, wake it up.
 	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(&proc->pendingInterrupts, interruptMask))
 		WakeupOtherProc(proc);
 }
 
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 612de8b35e9..447caa87962 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -1072,6 +1072,15 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	pgwin32_dispatch_queued_signals();
 #endif
 
+	/*
+	 * There are no CHECK_FOR_INTERRUPTS() calls below, but hold interrupts so
+	 * that if a signal is received, the signal handler doesn't try to change
+	 * CheckForInterruptsMask. That trips the assertion in
+	 * RECHECK_CFI_ATTENTION() that it isn't caled while the
+	 * SLEEPING_ON_INTERRUPTS flag is set.
+	 */
+	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
@@ -1084,26 +1093,24 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	 */
 	if (set->interrupt_mask != 0)
 	{
-		InterruptMask old_mask;
 		bool		already_pending = false;
 
 		/*
 		 * Perform a plain atomic read first as a fast path for the case that
 		 * an interrupt is already pending.
 		 */
-		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
-		already_pending = ((old_mask & set->interrupt_mask) != 0);
+		already_pending = InterruptPending(set->interrupt_mask);
 
 		if (!already_pending)
 		{
 			/*
-			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
-			 * an interrupt is already pending. The atomic op provides
-			 * synchronization so that if an interrupt bit is set after this,
-			 * the setter will wake us up.
+			 * Set the SLEEPING_ON_INTERRUPTS bit and re-check if an interrupt
+			 * is already pending. The atomic op provides synchronization so
+			 * that if an interrupt bit is set after setting the bit, the
+			 * setter will wake us up.
 			 */
-			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
-			already_pending = ((old_mask & set->interrupt_mask) != 0);
+			(void) pg_atomic_fetch_or_u32(&MyPendingInterrupts->flags, PI_FLAG_SLEEPING_ON_INTERRUPTS);
+			already_pending = InterruptPending(set->interrupt_mask);
 
 			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
 			sleeping_flag_armed = true;
@@ -1177,7 +1184,9 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 
 	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
 	if (sleeping_flag_armed)
-		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+		pg_atomic_fetch_and_u32(&MyPendingInterrupts->flags, ~PI_FLAG_SLEEPING_ON_INTERRUPTS);
+
+	RESUME_INTERRUPTS();
 
 #ifndef WIN32
 	waiting = false;
@@ -1255,7 +1264,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1414,7 +1423,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1539,7 +1548,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1760,7 +1769,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
index 239d58f7597..6118aedb025 100644
--- a/src/include/ipc/interrupt.h
+++ b/src/include/ipc/interrupt.h
@@ -23,14 +23,63 @@
  * needed which are needed by all callers of WaitInterrupt, so include it
  * here.
  *
- * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ * Note: InterruptMask is defined in waiteventset.h to avoid circular dependency
  */
 #include "storage/waiteventset.h"
 
+/*
+ * PendingInterrupts is a bitmask representing interrupts that are currently
+ * pending for a process, and some flags to help with signaling when the
+ * interrupt bits are altered.
+ *
+ * We support up to 64 different interrupts.  That way, the currently pending
+ * interrupts can be conveniently stored as on 64-bit atomic bitmask, on
+ * systems with 64-bit atomics.  On other systems, it's split into two 32-bit
+ * atomic fields.  That's good enough because we don't rely on atomicity
+ * between different interrupt bits.  (Note that the simulated 64-bit atomics
+ * are not good enough for this, because the simulation relies on spinlocks,
+ * which creates a deadlock risk when used from signal handlers.)
+ *
+ * Two signaling flags are defined:
+ *
+ * PI_FLAG_SLEEPING_ON_INTERRUPTS indicates that the backend is currently
+ * blocked waiting for an interrupt.  If set, the backend needs to be woken up
+ * when a bit in the 'interrupts' mask is set.
+ *
+ * PI_FLAG_CFI_ATTENTION indicates that some interrupt bits have been changed
+ * since the last ProcessInterupts() call.  It's set whenever any 'interrupts'
+ * bit is set.  It's checked by CHECK_FOR_INTERRUPTS() as a fastpath check to
+ * determine if there can be any interrupts pending that need processing, and
+ * cleared in ProcessInterrupts().  It must also be cleared / re-evaluated
+ * whenever CheckForInterruptsMask changes (see RECHECK_CFI_ATTENTION()).
+ */
+typedef struct
+{
+	pg_atomic_uint32 flags;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_uint64 interrupts;
+#else
+	pg_atomic_uint32 interrupts_lo;
+	pg_atomic_uint32 interrupts_hi;
+#endif
+} PendingInterrupts;
+
+#define PI_FLAG_SLEEPING_ON_INTERRUPTS	0x01
+#define PI_FLAG_CFI_ATTENTION			0x02
 
 /*
- * Flags in the pending interrupts bitmask. Each value is a different bit, so that
- * these can be conveniently OR'd together.
+ * 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;
+
+
+/*
+ * Interrupt bits that used in PendingIntrrupts->interrupts bitmask.  Each
+ * value is a different bit, so that these can be conveniently OR'd together.
  */
 #define UINT64_BIT(shift) (UINT64_C(1) << (shift))
 
@@ -191,18 +240,7 @@
  * extensions
  ***********************************************************************/
 #define BEGIN_ADDIN_INTERRUPTS 25
-#define END_ADDIN_INTERRUPTS 63
-
-/*
- * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
- * waiting for an interrupt.  If set, the backend needs to be woken up when a
- * bit in the pending interrupts mask is set.  It's used internally by the
- * interrupt machinery, and cannot be used directly in the public functions.
- * It's named differently to distinguish it from the actual interrupt flags.
- */
-#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
-
-extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+#define END_ADDIN_INTERRUPTS 64
 
 /*
  * Test an interrupt flag (or flags).
@@ -214,12 +252,25 @@ InterruptPending(InterruptMask interruptMask)
 	 * Note that there is no memory barrier here. This is used in
 	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
 	 *
+	 * FIXME: that's not true anymore, this is no longer called from
+	 * CHECK_FOR_INTERRUPTS(). Still seems correct and a good choice to not
+	 * force a barrier here. Just update the comment?
+	 *
 	 * That means that if the interrupt is concurrently set by another
 	 * process, we might miss it. That should be OK, because the next
 	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
 	 * We will see the updated value before sleeping.
 	 */
-	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+	uint64		pending;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_hi) << 32;
+#endif
+
+	return (pending & interruptMask) != 0;
 }
 
 /*
@@ -228,7 +279,14 @@ InterruptPending(InterruptMask interruptMask)
 static inline void
 ClearInterrupt(InterruptMask interruptMask)
 {
-	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+	uint64		mask = ~interruptMask;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	(void) pg_atomic_fetch_and_u64(&MyPendingInterrupts->interrupts, mask);
+#else
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_lo, (uint32) mask);
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_hi, (uint32) (mask >> 32));
+#endif
 }
 
 /*
@@ -310,13 +368,47 @@ extern void ProcessInterrupts(void);
 #define INTERRUPTS_CAN_BE_PROCESSED(mask) \
 	(((mask) & CheckForInterruptsMask) == (mask))
 
-/* Service interrupt, if one is pending and it's safe to service it now */
+/*
+ * Service an interrupt, if one is pending and it's safe to service it now.
+ *
+ * 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()					\
 do { \
-	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+	if (unlikely(pg_atomic_read_u32(&MyPendingInterrupts->flags) != 0)) \
 		ProcessInterrupts();											\
 } while(0)
 
+/*
+ * Set/clear the PI_FLAG_CFI_ATTENTION according to the currently pending
+ * interrupts and CheckForInterruptsMask. This should be called every time
+ * after enabling new bits in CheckForInterruptsMask, so that the next
+ * CHECK_FOR_INTERRUPTS() will react correctly to the newly enabled
+ * interrupts.
+ */
+static inline void
+RECHECK_CFI_ATTENTION(void)
+{
+	/*
+	 * 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_ON_INTERRUPTS) == 0);
+
+	/* clear the flag first */
+	(void) pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+
+	/*
+	 * Ensure that if a concurrent process sets an interrupt from now on, the
+	 * flag will be set again.
+	 */
+	pg_memory_barrier();
+
+	if (InterruptPending(CheckForInterruptsMask))
+		(void) pg_atomic_write_u32(&MyPendingInterrupts->flags, PI_FLAG_CFI_ATTENTION);
+}
 
 /*****************************************************************************
  *	  Critical section and interrupt holdoff mechanism
@@ -344,7 +436,10 @@ RESUME_INTERRUPTS(void)
 	Assert(InterruptHoldoffCount > 0);
 	InterruptHoldoffCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+	{
 		CheckForInterruptsMask = EnabledInterruptsMask;
+		RECHECK_CFI_ATTENTION();
+	}
 	else
 		Assert(CheckForInterruptsMask == 0);
 }
@@ -362,7 +457,10 @@ END_CRIT_SECTION(void)
 	Assert(CritSectionCount > 0);
 	CritSectionCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+	{
 		CheckForInterruptsMask = EnabledInterruptsMask;
+		RECHECK_CFI_ATTENTION();
+	}
 	else
 		Assert(CheckForInterruptsMask == 0);
 }
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 00ea9f86422..fab892f8b3c 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -325,7 +325,7 @@ struct PGPROC
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
 
 	/* Bit mask of pending interrupts, waiting to be processed */
-	pg_atomic_uint64 pendingInterrupts;
+	PendingInterrupts pendingInterrupts;
 
 	/*
 	 * While in hot standby mode, shows that a conflict signal has been sent
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a5d8ac10e9..bfb8a001d81 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2184,6 +2184,7 @@ PatternInfoArray
 Pattern_Prefix_Status
 Pattern_Type
 PendingFsyncEntry
+PendingInterrupts
 PendingListenAction
 PendingListenEntry
 PendingRelDelete
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-02-18 00:11               ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2026-02-20 14:22                 ` Heikki Linnakangas <[email protected]>
  2026-03-06 14:30                   ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-06-24 20:25                   ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  0 siblings, 2 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2026-02-20 14:22 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 18/02/2026 02:11, Heikki Linnakangas wrote:
> On 14/02/2026 23:56, Andres Freund wrote:
>> Could we have the mask of interrupts that WaitInterrupt() is waiting 
>> for in a
>> second variable? That way we could avoid interrupting WaitInterrupt() 
>> when
>> raising or sending a signal that WaitInterrupt() is not waiting for.  
>> I think
>> that can be one race-freely with a bit of care?
> 
> Yeah, I thought of that, but I'm not sure what the right tradeoff here 
> is. I doubt the spurious wakeups matter much in practice. Then again, 
> maybe it's not much more complicated, so maybe I should try that.
> 
> Now with this new version, the same consideration applies to the 
> CFI_ATTENTION flag I added. We could expose a process's 
> CheckForInterruptsMask alongside the pending interrupts, so it would be 
> SendInterrupt()'s responsibility to check if the receiving backend's 
> CheckForInterruptsMask includes the interrupt that's being sent. That 
> would similarly eliminate the "false positive" ProcessInterrupts() calls 
> from CHECK_FOR_INTERRUPTS(), by moving the logic to the senders. The 
> SLEEPING_ON_INTERRUPTS and CFI_ATTENTION flags are quite symmetrical.

I tried that approach, exposing an "attention bitmask" where a backend 
advertises which interrupts it's currently interested in. I think I like it.

Patch attached. The relevant changes for this "attention mechanism" are 
in the last patch, but there are some other small changes too so this 
split into patches is a little messy. I moved the list of standard 
interrupts to a separate header file, for example. So for reviewing, I 
recommend reading the resulting interrupt.h and interrupt.c files after 
applying all the patches, instead of trying to read the diff for those.

- Heikki


Attachments:

  [text/x-patch] v11-0001-Centralize-resetting-SIGCHLD-handler.patch (9.7K, ../../[email protected]/2-v11-0001-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From be81d957fe748215c8b82c0325b6e47930aa6f19 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Jan 2026 20:22:43 +0200
Subject: [PATCH v11 1/4] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6fde740465f..89c2b4f9500 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -411,7 +411,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1422,7 +1421,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 8678ea4e139..133af5aee97 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -781,7 +781,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0956bd39a85..dbd670c16cc 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -108,11 +108,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e03c19123bc..03aefa5b670 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -220,11 +220,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 82731e452fc..10a5ef9e15c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -237,9 +237,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cdbe53dd262..330c10ddb0b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -234,11 +234,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 86c5e376b40..8515bfb9653 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -283,11 +283,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 742137edad6..4e4eed4d076 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -263,11 +263,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 7c0e2809c17..8ca7b07e947 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -108,11 +108,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 062a08ccb88..5e1ee7e4728 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1536,7 +1536,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7c1b8757d7d..ec6c516530d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -257,9 +257,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2cde8ebc729..5acc8ee3ac8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3743,9 +3743,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..5d66c80567c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4285,13 +4285,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
-									 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 03f6c8479f2..fadf856d9bc 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -142,6 +142,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -154,6 +163,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] v11-0002-Refactor-how-some-aux-processes-advertise-their-.patch (9.6K, ../../[email protected]/3-v11-0002-Refactor-how-some-aux-processes-advertise-their-.patch)
  download | inline diff:
From 6dd10bba54c2f7bae7ea7bcaa7981d8a8c0a0eb6 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 14:23:35 +0200
Subject: [PATCH v11 2/4] Refactor how some aux processes advertise their
 ProcNumber

This moves the responsibility of setting the
ProcGlobal->walrewriterProc and checkpointerProc fields to
InitAuxiliaryProcess. Also switch to the same pattern to advertise the
autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
field in shared memory. This can easily be extended to other aux
processes in the future, if other processes need to find them.

Switch to pg_atomic_uint32 for the fields. Seems easier to reason
about than volatile pointers. There was some precedence for that, as
were already using pg_atomic_uint32 for the procArrayGroupFirst and
clogGroupFirst fields, which also store ProcNumbers.

TODO: could also replace WalRecv->procno with this
---
 src/backend/access/transam/xlog.c     |  3 +--
 src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
 src/backend/postmaster/checkpointer.c | 14 ++++---------
 src/backend/postmaster/walwriter.c    |  6 ------
 src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
 src/include/storage/proc.h            |  7 ++++---
 6 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13cce9b49f1..d12b1c02f3a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2653,8 +2653,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 
 	if (wakeup)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	walwriterProc = procglobal->walwriterProc;
+		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 89c2b4f9500..0a730fb5b45 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -277,7 +277,6 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -293,7 +292,6 @@ typedef struct AutoVacuumWorkItem
 typedef struct
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -563,8 +561,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		proc_exit(0);			/* done */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
-
 	/*
 	 * Create the initial database list.  The invariant we want this list to
 	 * keep is that it's ordered by decreasing next_worker.  As soon as an
@@ -798,8 +794,6 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
-
 	proc_exit(0);				/* done */
 }
 
@@ -1529,6 +1523,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (AutoVacuumShmem->av_startingWorker != NULL)
 	{
+		ProcNumber	launcherProc;
+
 		MyWorkerInfo = AutoVacuumShmem->av_startingWorker;
 		dbid = MyWorkerInfo->wi_dboid;
 		MyWorkerInfo->wi_proc = MyProc;
@@ -1547,8 +1543,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
+		if (launcherProc != INVALID_PROC_NUMBER)
+		{
+			int			pid = GetPGProcByNumber(launcherProc)->pid;
+
+			if (pid != 0)
+				kill(pid, SIGUSR2);
+		}
 	}
 	else
 	{
@@ -3391,7 +3393,6 @@ AutoVacuumShmemInit(void)
 
 		Assert(!found);
 
-		AutoVacuumShmem->av_launcherpid = 0;
 		dclist_init(&AutoVacuumShmem->av_freeWorkers);
 		dlist_init(&AutoVacuumShmem->av_runningWorkers);
 		AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 03aefa5b670..978a7d0e649 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,6 +47,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
@@ -343,12 +344,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	UpdateSharedMemoryConfig();
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->checkpointerProc = MyProcNumber;
-
 	/*
 	 * Loop until we've been asked to write the shutdown checkpoint or
 	 * terminate.
@@ -1120,7 +1115,7 @@ RequestCheckpoint(int flags)
 	for (ntries = 0;; ntries++)
 	{
 		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 		if (checkpointerProc == INVALID_PROC_NUMBER)
 		{
@@ -1261,8 +1256,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	/* ... but not till after we release the lock */
 	if (too_full)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
@@ -1543,7 +1537,7 @@ void
 WakeupCheckpointer(void)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
-	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
 		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 8ca7b07e947..1851417ad1b 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -200,12 +200,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	hibernating = false;
 	SetWalWriterSleeping(false);
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->walwriterProc = MyProcNumber;
-
 	/*
 	 * Loop forever
 	 */
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index fd8318bdf3d..ba7eaed1a13 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -211,8 +211,9 @@ InitProcGlobal(void)
 	dlist_init(&ProcGlobal->bgworkerFreeProcs);
 	dlist_init(&ProcGlobal->walsenderFreeProcs);
 	ProcGlobal->startupBufferPinWaitBufId = -1;
-	ProcGlobal->walwriterProc = INVALID_PROC_NUMBER;
-	ProcGlobal->checkpointerProc = INVALID_PROC_NUMBER;
+	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->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -709,6 +710,14 @@ InitAuxiliaryProcess(void)
 	 */
 	PGSemaphoreReset(MyProc->sem);
 
+	/* Some aux processes are also advertised in ProcGlobal */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
+	if (MyBackendType == B_WAL_WRITER)
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
+	if (MyBackendType == B_CHECKPOINTER)
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+
 	/*
 	 * Arrange to clean up at process exit.
 	 */
@@ -1053,6 +1062,23 @@ AuxiliaryProcKill(int code, Datum arg)
 	SwitchBackToLocalLatch();
 	pgstat_reset_wait_event_storage();
 
+	/* If this was one of aux processes advertised in ProcGlobal, clear it */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->avLauncherProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_WAL_WRITER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walwriterProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_CHECKPOINTER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	}
+
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 23e5cd98161..1f1ab7d02ca 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -445,11 +445,12 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 clogGroupFirst;
 
 	/*
-	 * Current slot numbers of some auxiliary processes. There can be only one
+	 * Current proc numbers of some auxiliary processes. There can be only one
 	 * of each of these running at a time.
 	 */
-	ProcNumber	walwriterProc;
-	ProcNumber	checkpointerProc;
+	pg_atomic_uint32 avLauncherProc;
+	pg_atomic_uint32 walwriterProc;
+	pg_atomic_uint32 checkpointerProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
-- 
2.47.3



  [text/x-patch] v11-0003-Replace-Latches-with-Interrupts.patch (498.5K, ../../[email protected]/4-v11-0003-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From a26b8937b17e407738eb49f95ecb2f421cba1534 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Feb 2026 17:07:28 +0200
Subject: [PATCH v11 3/4] Replace Latches with Interrupts

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

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

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

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

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

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

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() clears the interrupts bits that it
processes, the caller is no longer required to do ClearInterrupt (or
ResetLatch) for those bits. If you wake up for other interrupts that
are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear
those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_WAIT_WAKEUP interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

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

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

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

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/[email protected]
  Discussion: https://www.postgresql.org/message-id/[email protected]

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

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

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

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

XXX: original text from Thomas's patch
--------------------------------------

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

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

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 89e187425cc..6be5ca9c183 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -170,10 +168,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. */
@@ -222,27 +223,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
 		{
@@ -266,14 +267,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);
 	}
 
 	/*
@@ -423,7 +423,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;
 
 		/*
@@ -444,7 +444,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)));
@@ -504,8 +504,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. */
@@ -923,9 +922,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),
@@ -958,9 +954,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index add673a4776..a1c8016d0f3 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -830,8 +830,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);
@@ -1484,7 +1484,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))
@@ -1579,7 +1579,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))
@@ -1687,12 +1687,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 				pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 			/* Sleep until there's something to do */
-			wc = WaitLatchOrSocket(MyLatch,
-								   WL_LATCH_SET | WL_SOCKET_READABLE |
-								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								   PQsocket(conn),
-								   cur_timeout, pgfdw_we_cleanup_result);
-			ResetLatch(MyLatch);
+			wc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_INTERRUPT | WL_SOCKET_READABLE |
+									   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									   PQsocket(conn),
+									   cur_timeout, pgfdw_we_cleanup_result);
 
 			CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 60d90329a65..eb925fda39e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2affba74382..06bc57f10bd 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -63,7 +63,7 @@ typedef struct BackgroundWorker
     char        bgw_function_name[BGW_MAXLEN];
     Datum       bgw_main_arg;
     char        bgw_extra[BGW_EXTRALEN];
-    pid_t       bgw_notify_pid;
+    pid_t       bgw_notify_pid;			/* not used */
 } BackgroundWorker;
 </programlisting>
   </para>
@@ -131,6 +131,21 @@ typedef struct BackgroundWorker
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>BGWORKER_NO_NOTIFY</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BGWORKER_NO_NOTIFY</primary></indexterm> Normally,
+       the backend that registers a dynamic worker will be notified
+       with <literal>INTERRUPT_WAIT_WAKEUP</literal> when the worker's state
+       changes, which allows the caller to wait for the worker to start and
+       shut down.  That can be suppressed by setting this flag.
+       <!-- FIXME: Should we reserve a dedicated INTERRUPT bit for it? And if
+            we do, do we even need the BGWORKER_NO_NOTIFY flag, there's very little
+            overhead in sending a useless interrupt -->
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -204,12 +219,8 @@ typedef struct BackgroundWorker
   </para>
 
   <para>
-   <structfield>bgw_notify_pid</structfield> is the PID of a PostgreSQL
-   backend process to which the postmaster should send <literal>SIGUSR1</literal>
-   when the process is started or exits.  It should be 0 for workers registered
-   at postmaster startup time, or when the backend registering the worker does
-   not wish to wait for the worker to start up.  Otherwise, it should be
-   initialized to <literal>MyProcPid</literal>.
+   <structfield>bgw_notify_pid</structfield> is not used and may be removed
+   in a future release.
   </para>
 
   <para>Once running, the process can connect to a database by calling
@@ -260,7 +271,7 @@ typedef struct BackgroundWorker
    reinitializes the cluster due to a backend failure.  Backends which need
    to suspend execution only temporarily should use an interruptible sleep
    rather than exiting; this can be achieved by calling
-   <function>WaitLatch()</function>. Make sure the
+   <function>WaitInterrupt()</function>. Make sure the
    <literal>WL_POSTMASTER_DEATH</literal> flag is set when calling that function, and
    verify the return code for a prompt exit in the emergency case that
    <command>postgres</command> itself has terminated.
@@ -291,10 +302,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 05642dc02e3..98819087871 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..e8e84e248ce 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index c5d7db28077..06c996f6fc0 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb65a..ddf414660e0 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index ff927279cc3..460cd9f9d95 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index c9f143f6c31..b2ac6e247e6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index dfffce3e396..9fc03134804 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 11b214eb99b..9e236799f2e 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 632c2427952..9cb4b766795 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 4be267ff657..ae7e7993c8b 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,6 +143,7 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
@@ -3300,11 +3301,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 3047bd46def..da1952e9412 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..70a4e4ee00a 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 44786dc131f..65c467f25f9 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -114,9 +115,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -126,9 +124,6 @@ static FixedParallelState *MyFixedParallelState;
 /* List of active parallel contexts. */
 static dlist_head pcxt_list = DLIST_STATIC_INIT(pcxt_list);
 
-/* Backend-local copy of data from FixedParallelState. */
-static pid_t ParallelLeaderPid;
-
 /*
  * List of internal parallel worker entry points.  We need this for
  * reasons explained in LookupParallelWorkerFunction(), below.
@@ -242,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -611,7 +606,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.
@@ -766,16 +760,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();
+				}
 			}
 		}
 
@@ -884,15 +883,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1033,21 +1033,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1082,9 +1067,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1326,7 +1309,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. */
@@ -1362,8 +1348,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));
 
@@ -1379,8 +1364,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
@@ -1619,9 +1603,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..5592df30f8d 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index eabc4d48208..2477618e870 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index d12b1c02f3a..8840cc43618 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2656,7 +2656,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);
 	}
 }
 
@@ -9519,11 +9519,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2efe4105efb..8c74eceec18 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -24,11 +24,11 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.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"
@@ -718,17 +718,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index fc794693442..be7ab212ac0 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -322,23 +323,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.
 	 */
@@ -473,7 +457,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -547,13 +530,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).
@@ -1656,13 +1632,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);
 }
 
 /*
@@ -1792,8 +1761,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
@@ -1822,8 +1791,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))
 			{
@@ -1844,9 +1813,8 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2979,7 +2947,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;
 
@@ -3056,8 +3024,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)
@@ -3065,11 +3033,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;
 
@@ -3090,10 +3063,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;
 }
@@ -3771,16 +3746,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;
@@ -4046,11 +4021,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;
 				}
 
@@ -4066,11 +4042,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 */
@@ -4502,11 +4475,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4544,7 +4517,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);
 }
 
 /*
@@ -4748,7 +4724,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 d286ff63123..efe08c4ebfe 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -68,7 +68,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -241,9 +241,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
@@ -298,14 +298,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);
 }
@@ -364,7 +363,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.
@@ -376,7 +375,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);
@@ -434,8 +433,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
@@ -447,8 +447,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index e112eed7485..e49fdc1efa2 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 657c591618d..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  sets the process's latch, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  main loop in PostgresBackend()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,6 +166,7 @@
 #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"
@@ -177,7 +174,6 @@
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -288,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -318,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -527,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. ProcessNotifyInterrupt() will then be called whenever it's safe to
- * actually deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -552,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1265,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1360,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1386,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1618,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1638,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2242,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2262,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2293,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2304,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2330,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2340,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2360,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2371,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, signalProcnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 }
 
@@ -2530,39 +2496,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted
- *		via the process's latch, and this routine will get called.
- *		If we are truly idle (ie, *not* inside a transaction block),
- *		process the incoming notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if the interrupt occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -2571,12 +2510,13 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
-	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2686,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3050,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index 94d6f415a06..5005030cd7b 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -269,9 +269,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)
@@ -303,7 +307,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 03932f45c8a..cc34f40f2ac 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2430,8 +2430,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2440,9 +2439,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();
 	}
@@ -2529,8 +2528,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 7138dc692c6..166b6f5eec6 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,9 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
+#include "utils/resowner.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1043,7 +1043,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;
@@ -1067,8 +1067,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,
@@ -1078,8 +1078,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)
@@ -1122,14 +1122,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 4105f1d1968..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,7 +34,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "utils/wait_event.h"
 
@@ -382,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..459f3d3cae4
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,260 @@
+Interrupts
+==========
+
+"Interrupts" are a set of flags that represent conditions that should
+be handled at a later time.  They are roughly analogous to Unix
+signals, except that they are handled cooperatively by checking for
+them at many points in the code. The CHECK_FOR_INTERRUPTS() macro is
+called at strategically located spots where it is normally safe to
+process interrupts.
+
+Well behaved backend code calls CHECK_FOR_INTERRUPTS() periodically in
+any long-running loops or other long computations, and never sleeps
+using mechanisms other than the WaitEventSet mechanism or the more
+convenient WaitInterrupt / WaitSockerOrInterrupt functions (except for
+bounded short periods, eg LWLock waits). Following these rules ensures
+that the backend doesn't become uncancellable for too long.
+
+Each interrupt type can have a handler function associated with it,
+which will be called by CHECK_FOR_INTERRUPTS() if the interrupt is
+pending.  A standard set of interrupt handlers are installed at
+process startup, but they can be overridden by
+SetInterruptHandler(). A handler can also be temporarily disabled by
+calling DisableInterrupt and re-enabled by EnableInterrupt.
+
+If an interrupt is raised that doesn't have a handler function or it
+is not enabled, the interrupt will remain pending until it is cleared,
+or the handler is re-enabled.
+
+If an interrupt is sent that was already pending for the target
+process, it is coalesced, ie. the target process will process it only
+once. Attempting to set an interrupt bit that is already set is very
+fast, amounting to merely an atomic op in shared memory.
+
+See src/include/ipc/interrupt.h for a list of all the interrupts used
+in core code. Extensions can define their own interrupts.
+
+HOLD/RESUME_INTERRUPTS
+----------------------
+
+Processing an interrupt may throw an error with ereport() (for
+example, query cancellation) or terminate the process gracefully. In
+some cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level
+subroutines that might sometimes be called in contexts that do *not*
+want to allow interrupt processing.  The HOLD_INTERRUPTS() and
+RESUME_INTERRUPTS() macros allow code to ensure that no interrupt
+handlers are called, even if CHECK_FOR_INTERRUPTS() gets called in a
+subroutine.  The interrupt will be held off until
+CHECK_FOR_INTERRUPTS() is done outside any HOLD_INTERRUPTS() ...
+RESUME_INTERRUPTS() section.
+
+HOLD_INTERRUPTS() blocks can be nested. If HOLD_INTERRUPTS() is called
+multiple times, interrupts are held until every HOLD_INTERRUPTS() call
+has been balanced with a RESUME_INTERRUPTS() call.
+
+Critical sections
+-----------------
+
+A related, but conceptually distinct, mechanism is the "critical
+section" mechanism.  A critical section not only holds off cancel/die
+interrupts, but causes any ereport(ERROR) or ereport(FATAL) to become
+ereport(PANIC) --- that is, a system-wide reset is forced.  Needless
+to say, only really *critical* code should be marked as a critical
+section! This mechanism is mostly used for XLOG-related code.
+
+Sending interrupts
+------------------
+
+Interrupt flags can be "raised" in different ways:
+- synchronously by code that wants to defer an action, by calling
+  RaiseInterrupt,
+- asynchronously by timers or other signal handlers, also by calling
+  RaiseInterrupt, or
+- "sent" by other backends with SendInterrupt
+
+Processing interrupts
+---------------------
+
+Interrupts are usually handled by handler functions, which are called
+from CHECK_FOR_INTERRUPTS() when the corresponding interrupt is
+pending. Use SetInterruptHandler() to install a handler function.
+
+Sometimes you don't want an interrupt to be processed at any random
+CHECK_FOR_INTERRUPTS() invocation. In that case, you can leave the
+interrupt handler disabled, and check for the interrupt explicitly at
+suitable spots with InterruptPending().
+
+INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
+interrupt needs to be serviced, without trying to do so immediately.
+Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
+which tells whether CHECK_FOR_INTERRUPTS() is sure to clear the
+interrupt.
+
+
+Waiting for an interrupt
+------------------------
+The correct pattern to wait for an event(s) that raises an interrupt
+is:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	/* Check if our interrupt was raised (clearing it if it was) */
+	if (ConsumeInterrupt(INTERRUPT_PRINTER_ON_FIRE))
+		break;
+
+	/*
+	 * Sleep until our interrupt or one of the standard interrupts is
+	 * received.
+	 */
+	WaitInterrupt(INTERRUPT_CFI_MASK |
+				  INTERRUPT_PRINTER_ON_FIRE,
+				  ...);
+}
+```
+
+Often an interrupt is used to wake up a process that is known to be
+waiting and not doing anything else. The INTERRUPT_WAIT_WAKEUP
+interrupt is reserved for such use cases where you don't need a
+dedicated interrupt flag. When using INTERRUPT_WAIT_WAKEUP, you need
+some other signaling, e.g. a flag in shared memory, to indicate that
+the desired event has happen, as you may sometimes receive false
+wakeups. The pattern using INTERRUPT_WAIT_WAKEUP looks like this:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+}
+```
+
+It's important to clear the interrupt *before* checking if there's
+work to do. Otherwise, if someone sets the interrupt between the check
+and the ClearInterrupt() call, you will miss it and Wait will
+incorrectly block. Another valid coding pattern looks like:
+
+```
+for (;;)
+{
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+}
+```
+
+This is useful to reduce interrupt traffic if it's expected that the
+loop's termination condition will often be satisfied in the first
+iteration; the cost is an extra loop iteration before blocking when it
+is not.  What must be avoided is placing any checks for asynchronous
+events after WaitInterrupt and before ClearInterrupt, as that creates
+a race condition.
+
+Custom interrupts in extensions
+-------------------------------
+
+An extension can allocate interrupt bits for its own purposes by
+calling RequestAddinInterrupt(). Note that interrupts are a somewhat
+scarce resource, so consider using INTERRUPT_WAIT_WAKEUP if all you
+need is a simple wakeup in some loop.
+
+
+Unix Signals
+============
+
+Postmaster and most child processes also respond to a few standard
+Unix signals:
+
+SIGHUP -> Raises INTERRUPT_CONFIG_RELOAD
+SIGINT -> Raises INTERRUPT_QUERY_CANCEL
+SIGTERM -> Raises INTERRUPT_TERMINATE
+SIGQUIT -> immediate shutdown, abort the process
+
+pg_ctl uses these Unix signals to tell postmaster to reload config,
+stop, etc. These are also mentioned in the user documentation.
+
+Postmaster cannot send interrupts to processes without PGPROC entries
+(just syslogger nowadays), and it doesn't know the PGPROC entries of
+other child processes anyway. Hence, it still uses the above Unix
+signals for postmaster -> child signaling. The only exception is when
+postmaster notifies a backend that a bgworker it launched has
+exited. Postmaster sends that interrupt directly. The backend
+registers explicitly for that notification, and supplies the
+ProcNumber to postmaster when registering.
+
+There are a few more exceptions:
+
+- A few processes like the checkpointer, archiver, and IO workers
+  don't react to SIGTERM, because Unix system shutdown sends a SIGTERM
+  to all processes, and we want them to exit only only after all other
+  process. Instead, the postmaster sends them SIGUSR2 after all the
+  other processes have exited.
+
+- Child processes use SIGUSR1 to request the postmaster to do various
+  actions or notify that some event has occurred. See pmsignal.c for
+  details on that mechanism
+
+
+TODO: Open questions
+====================
+
+TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
+----------------------------------------------------
+
+Postmaster cannot send interrupts to processes without PGPROC
+entries. Hence, it still uses plain Unix signals. Would be nice if
+postmaster could send interrupts directly. If we moved the interrupt
+mask to PMChildSlot, it could.  However, PGPROC seems like a more
+natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+d) Keep it as it is, continue to use signals for postmaster -> child
+   signaling
+
+
+TODO: Unique session id
+----------------------
+
+In some places, we read the pid of a process (from PGPROC or
+elsewhere), and send signal to it, accepting that the process may have
+already exited.  If we directly replace those uses of 'pid' with a
+ProcNumber, the ProcNumber might get reused much faster than the pid
+would. Solutions:
+
+a) Introduce the concept of a unique session ID that is never recycled
+   (or not for a long time, anyway). When reading the ProcNumber to
+   send an interrupt to, also read the session ID. When sending the
+   interrupt, check that the session ID matches.
+
+
+TODO: Other
+-----------
+
+- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
+  it's nevertheless more instructions than it used to be.
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
new file mode 100644
index 00000000000..61d148409bc
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,431 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint64 LocalPendingInterrupts;
+
+pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
+
+/*
+ * Install an interrupt handler callback function for the given interrupt.
+ *
+ * You need to also enable the interrupt with EnableInterrupt(), unless you're
+ * replacing an existing handler function.
+ */
+void
+SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler)
+{
+	/*
+	 * XXX: It's somewhat inefficient to loop through all the bits, but this
+	 * isn't performance critical.
+	 */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+		{
+			/* Replace old handler */
+			interrupt_handlers[i] = handler;
+		}
+	}
+}
+
+/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
+void
+EnableInterrupt(InterruptMask interruptMask)
+{
+#ifdef USE_ASSERT_CHECKING
+	/* Check that the interrupt has a handler defined */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
+ *
+ * If an interrupt condition is pending, and it's safe to service it,
+ * then clear the flag and call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning, if it was set when entering.  (This is not the same as
+ * guaranteeing that it's still clear when we return; another interrupt could
+ * have arrived.  But we promise that any pre-existing one will have been
+ * serviced.)
+ */
+void
+ProcessInterrupts(void)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/* Check once what interrupts are pending */
+	interruptsToProcess =
+		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		{
+			/*
+			 * Clear the interrupt *before* calling the handler function, so
+			 * that if the interrupt is received again while the handler
+			 * function is being executed, we won't miss it.
+			 *
+			 * For similar reasons, we also clear the flags one by one even if
+			 * multiple interrupts are pending.  Otherwise if one of the
+			 * interrupt handlers bail out with an ERROR, we would have
+			 * already cleared the other bits, and would miss processing them.
+			 */
+			ClearInterrupt(UINT64_BIT(i));
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint64		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint64		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in some aux processes that
+ * want to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
+
+/* Reserve an interrupt bit for use in an extension */
+InterruptMask
+RequestAddinInterrupt(void)
+{
+	InterruptMask result;
+
+	if (nextAddinInterruptBit == END_ADDIN_INTERRUPTS)
+		elog(ERROR, "out of addin interrupt bits");
+
+	result = UINT64_BIT(nextAddinInterruptBit);
+	nextAddinInterruptBit++;
+	return result;
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..589f755622a
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 *
+	 * The process may ignore the interrupts that these raise, e.g if query
+	 * cancellation is not applicable.  But there's no harm in having the
+	 * signal handlers in place anyway.
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is sent by postmaster to some aux processes, for different
+	 * purposes.  Such processes override this before unblocking signals, but
+	 * ignore it by default.
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/*
+	 * SIGALRM is used for timeouts, but the handler is established later in
+	 * InitializeTimeouts()
+	 */
+	pqsignal(SIGALRM, SIG_IGN);
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e04aa2e68ed..f8fbdd090d4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index df7dc79b827..1a60ca8f78a 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,6 +15,7 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
@@ -421,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.
  *
@@ -457,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
@@ -496,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
@@ -678,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 4da6ac22ff9..3a0ff956823 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -533,8 +534,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 3f9257ab010..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,8 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -174,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -181,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -213,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -228,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -255,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -284,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -300,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -307,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -338,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -349,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -364,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 6570f27297b..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,8 +73,8 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
@@ -175,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);
@@ -287,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
@@ -306,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;
 }
@@ -1411,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;
 		}
 
@@ -2062,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 6e4bbfb5aa1..0a029eb02a8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,6 +14,7 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -25,7 +26,6 @@
 
 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);
@@ -75,14 +75,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;
 }
 
@@ -169,27 +168,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
+			SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+						  pq_mq_parallel_leader_proc_number);
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 712a857cdb4..d20e6fba285 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0a730fb5b45..5763057a87a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -27,15 +27,17 @@
  * launcher has set up.
  *
  * If the fork() call fails in the postmaster, it sets a flag in the shared
- * memory area, and sends a signal to the launcher.  The launcher, upon
- * noticing the flag, can try starting the worker again by resending the
+ * memory area, sends a signal to the launcher, raising the
+ * INTERRUPT_AUTOVACUUM_WORKER_FINISHED interrupt. The launcher, upon
+ * noticing the interrupt, can try starting the worker again by resending the
  * signal.  Note that the failure can only be transient (fork failure due to
  * high load, memory pressure, too many processes, etc); more permanent
  * problems, like failure to connect to a database, are detected later in the
  * worker and dealt with just by having the worker exit normally.  The launcher
  * will launch a new worker again later, per schedule.
  *
- * When the worker is done vacuuming it sends SIGUSR2 to the launcher.  The
+ * When the worker is done vacuuming and exits, the postmaster sends SIGUSR2
+ * to the launcher, raising INTERRUPT_AUTOVACUUM_WORKER_FINISHED.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -151,9 +153,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;
@@ -287,6 +286,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
@@ -322,7 +324,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);
@@ -394,21 +396,23 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * Set up signal handlers.  We operate on databases much like a regular
-	 * backend, so we use the same signal handling.  See equivalent code in
-	 * tcop/postgres.c.
+	 * Set up signal and interrupt handlers.  We operate on databases much
+	 * like a regular backend, so we use mostly the same handling.  See
+	 * equivalent code in tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
+	SetStandardInterruptHandlers();
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Postmaster uses SIGUSR2 to raise INTERRUPT_AUTOVACUUM_WORKER_FINISHED */
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
+
+	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -455,7 +459,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();
@@ -497,7 +502,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();
 
 		/*
@@ -556,7 +561,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 */
 	}
@@ -571,41 +576,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -741,21 +749,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? */
@@ -773,17 +777,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();
 }
 
 /*
@@ -1350,8 +1343,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)
@@ -1363,8 +1356,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1399,22 +1391,18 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-
-	/*
-	 * 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, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	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
@@ -1545,12 +1533,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
 	{
@@ -2292,9 +2275,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);
 
 			/*
@@ -2452,7 +2434,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();
 		{
@@ -2541,9 +2523,8 @@ deleted:
 		 * Check for config changes before acquiring lock for further jobs.
 		 */
 		CHECK_FOR_INTERRUPTS();
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			VacuumUpdateCosts();
 		}
@@ -2665,7 +2646,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 8ea800c0bbd..ee16e9cb025 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 133af5aee97..20b7ba737ba 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -79,6 +79,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;
 
 /*
@@ -202,8 +204,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -244,6 +247,19 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	/*
+	 * FIXME: be extra paranoid and sanity check the proc number, since this
+	 * runs in the postmaster
+	 */
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -325,20 +341,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;
@@ -346,8 +362,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;
 		}
@@ -393,23 +409,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;
@@ -431,7 +430,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.
  */
@@ -476,8 +475,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);
 }
 
 /*
@@ -493,12 +492,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
@@ -511,27 +510,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;
+		}
 	}
 }
 
@@ -563,14 +569,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);
 		}
 	}
 }
@@ -624,11 +630,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;
 		}
 	}
 }
@@ -756,32 +757,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, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -987,15 +984,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
@@ -1111,6 +1099,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.
@@ -1211,8 +1218,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)
@@ -1232,9 +1240,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)
 		{
@@ -1242,7 +1250,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1256,8 +1264,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)
@@ -1275,9 +1284,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)
 		{
@@ -1285,7 +1294,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 dbd670c16cc..a53596d49ef 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * Set up interrupt handling
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -220,9 +216,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.
@@ -295,22 +291,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
@@ -325,10 +321,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 978a7d0e649..054872aabce 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -11,8 +11,10 @@
  * condition.)
  *
  * The normal termination sequence is that checkpointer is instructed to
- * execute the shutdown checkpoint by SIGINT.  After that checkpointer waits
- * to be terminated via SIGUSR2, which instructs the checkpointer to exit(0).
+ * execute the shutdown checkpoint by SIGINT, which raises the
+ * INTERRUPT_SHUTDOWN_XLOG interrupt.  After that checkpointer waits
+ * to be terminated via SIGUSR2, raising INTERRUP_TERMINATE, which instructs
+ * the checkpointer to exit(0).
  * All backends must be stopped before SIGINT or SIGUSR2 is issued!
  *
  * Emergency termination is by SIGQUIT; like any backend, the checkpointer
@@ -44,13 +46,14 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -85,10 +88,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
@@ -163,7 +167,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;
@@ -175,7 +178,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);
@@ -205,22 +208,28 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	CheckpointerShmem->checkpointer_pid = MyProcPid;
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * Note: we deliberately ignore SIGTERM, because during a standard Unix
-	 * system shutdown cycle, init will SIGTERM all processes at once.  We
-	 * want to wait for the backends to exit, whereupon the postmaster will
-	 * tell us it's okay to shut down (via SIGUSR2).
+	 * Ignore SIGTERM, because during a standard Unix system shutdown cycle,
+	 * init will SIGTERM all processes at once.  We want to wait for the
+	 * backends to exit, whereupon the postmaster will tell us it's okay to
+	 * shut down (via INTERRUPT_SHUTDOWN_XLOG).
+	 */
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessCheckpointerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
+	/* we will check for INTERRUPT_TERMINATE explicitly in the loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
@@ -359,15 +368,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;
 
 		/*
@@ -541,10 +550,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;
 		}
 
@@ -569,7 +578,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);
@@ -585,10 +594,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);
 	}
 
 	/*
@@ -597,7 +609,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -615,7 +627,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);
 	}
 
 	/*
@@ -625,55 +637,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();
 }
 
 /*
@@ -785,24 +780,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;
 
@@ -817,10 +806,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)
 	{
@@ -832,10 +823,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -924,12 +911,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);
 }
 
 
@@ -1102,14 +1088,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++)
@@ -1128,7 +1115,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1259,7 +1246,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;
@@ -1540,5 +1527,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * Simple signal handler for triggering a configuration reload.
- *
- * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending should be checked at a convenient place within the
- * main loop, or else the main loop should call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForShutdownRequest(SIGNAL_ARGS)
-{
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 10a5ef9e15c..1f4c3576e5b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -132,11 +132,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
  * ----------
@@ -148,10 +143,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -225,18 +221,27 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install an interrupt handler for it?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -247,8 +252,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;
 
@@ -282,13 +287,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);
 }
 
 
@@ -297,8 +301,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);
 }
 
 /*
@@ -309,7 +312,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
@@ -318,13 +321,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -334,7 +339,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);
 
@@ -346,6 +351,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -356,10 +362,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -408,7 +417,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;
 
 			/*
@@ -416,7 +425,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;
@@ -849,30 +858,13 @@ pgarch_die(int code, Datum arg)
 	PgArch->pgprocno = INVALID_PROC_NUMBER;
 }
 
-/*
- * Interrupt handler for WAL archiver process.
- *
- * This is called in the loops pgarch_MainLoop and pgarch_ArchiverCopyLoop.
- * It checks for barrier events, config update and request for logging of
- * memory contexts, but not shutdown request because how to handle
- * shutdown request is different between those loops.
- */
 static void
-ProcessPgArchInterrupts(void)
+pgarch_ProcessConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ConfigReloadPending)
 	{
 		char	   *archiveLib = pstrdup(XLogArchiveLibrary);
 		bool		archiveLibChanged;
 
-		ConfigReloadPending = false;
 		ProcessConfigFile(PGC_SIGHUP);
 
 		if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3fac46c402b..6efc5696996 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -539,10 +539,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +557,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +577,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1644,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1681,20 @@ ServerLoop(void)
 								   0 /* postmaster posts no wait_events */ );
 
 		/*
-		 * Latch set by signal handler, or new connection pending on any of
-		 * our sockets? If the latter, fork a child process to deal with it.
+		 * Interrupt raised by signal handler, or new connection pending on
+		 * any of our sockets?  If the latter, fork a child process to deal
+		 * with it.
 		 */
 		for (int i = 0; i < nevents; i++)
 		{
-			if (events[i].events & WL_LATCH_SET)
-				ResetLatch(MyLatch);
+			if (events[i].events & WL_INTERRUPT)
+				ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/*
 			 * The following requests are handled unconditionally, even if we
-			 * didn't see WL_LATCH_SET.  This gives high priority to shutdown
-			 * and reload requests where the latch happens to appear later in
-			 * events[] or will be reported by a later call to
+			 * didn't see WL_INTERRUPT.  This gives high priority to shutdown
+			 * and reload requests where the interrupt event happens to appear
+			 * later in events[] or will be reported by a later call to
 			 * WaitEventSetWait().
 			 */
 			if (pending_pm_shutdown_request)
@@ -1985,7 +1986,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1996,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2073,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2234,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2581,6 +2582,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2630,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2658,14 @@ CleanupBackend(PMChild *bp,
 	}
 
 	/*
-	 * This backend may have been slated to receive SIGUSR1 when some
-	 * background worker started or stopped.  Cancel those notifications, as
-	 * we don't want to signal PIDs that are not PostgreSQL backends.  This
+	 * This backend may have been slated to receive INTERRUPT_WAIT_WAKEUP when
+	 * some background worker started or stopped.  Cancel those notifications,
+	 * as we don't want to signal PIDs that are not PostgreSQL backends.  This
 	 * gets skipped in the (probably very common) case where the backend has
 	 * never requested any such notifications.
 	 */
 	if (bp_bgworker_notify)
-		BackgroundWorkerStopNotifications(bp_pid);
+		BackgroundWorkerStopNotifications(bp_child_slot);
 
 	/*
 	 * If it was an autovacuum worker, wake up the launcher so that it can
@@ -3921,7 +3924,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4296,15 +4299,15 @@ maybe_start_bgworkers(void)
 		{
 			if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 			{
-				int			notify_pid;
+				ProcNumber	notify_proc_number;
 
-				notify_pid = rw->rw_worker.bgw_notify_pid;
+				notify_proc_number = GetNotifyProcNumberForRegisteredWorker(rw);
 
 				ForgetBackgroundWorker(rw);
 
 				/* Report worker is gone now. */
-				if (notify_pid != 0)
-					kill(notify_pid, SIGUSR1);
+				if (notify_proc_number != INVALID_PROC_NUMBER)
+					SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 330c10ddb0b..bb23642b73f 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * Flags set by interrupt handlers for later service in the redo loop.
- */
-static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t shutdown_requested = false;
-static volatile sig_atomic_t promote_signaled = false;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -77,7 +73,7 @@ int			log_startup_progress_interval = 10000;	/* 10 sec */
 
 /* Signal handlers */
 static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
+static void StartupProcShutdownHandler(SIGNAL_ARGS);
 
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
@@ -92,16 +88,12 @@ static void StartupProcExit(int code, Datum arg);
 static void
 StartupProcTriggerHandler(SIGNAL_ARGS)
 {
-	promote_signaled = true;
-	WakeupRecovery();
-}
-
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-StartupProcSigHupHandler(SIGNAL_ARGS)
-{
-	got_SIGHUP = true;
-	WakeupRecovery();
+	/*
+	 * The INTERRUPT_CHECK_PROMOTE flag hints the startup process to check for
+	 * the signal file, while INTERRUPT_WAL_ARRIVED wakes up the process from
+	 * sleep.
+	 */
+	RaiseInterrupt(INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -111,8 +103,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +122,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +150,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +169,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -225,15 +202,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -241,6 +216,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
 	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
 
+	/* Only set up a subset of the standard interrupt handlers */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessStartupProcTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessStartupProcConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
 	/*
 	 * Unblock signals (they were blocked when the postmaster forked us)
 	 */
@@ -268,7 +250,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -278,18 +260,6 @@ PostRestoreCommand(void)
 	in_restore_command = false;
 }
 
-bool
-IsPromoteSignaled(void)
-{
-	return promote_signaled;
-}
-
-void
-ResetPromoteSignaled(void)
-{
-	promote_signaled = false;
-}
-
 /*
  * Set a flag indicating that it's time to log a progress report.
  */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 8515bfb9653..c9faa76b6a9 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -272,16 +272,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -322,7 +320,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
@@ -332,9 +330,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 */
@@ -350,14 +351,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);
 
 			/*
@@ -1182,7 +1182,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);
 	}
@@ -1193,8 +1193,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();
@@ -1585,9 +1585,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 4e4eed4d076..d8c754ad280 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -31,17 +31,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -146,7 +146,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);
@@ -240,16 +240,15 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 			(errmsg_internal("WAL summarizer started")));
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -309,10 +308,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) */
@@ -349,7 +346,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();
@@ -624,8 +621,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)
@@ -640,7 +637,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -849,27 +846,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();
 }
 
 /*
@@ -1008,7 +995,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);
@@ -1495,7 +1482,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1533,7 +1520,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. */
@@ -1633,11 +1620,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1684,7 +1675,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,
@@ -1703,7 +1694,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 1851417ad1b..93dfc98cc34 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,14 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -209,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))
 		{
@@ -222,11 +221,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -250,9 +248,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 1730ace5490..bfdb07a3b79 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,10 +157,12 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
@@ -238,12 +240,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -706,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)
@@ -759,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);
@@ -805,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);
 			}
 		}
 		else
@@ -844,9 +831,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -871,15 +857,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();
 
 	/*
@@ -940,8 +929,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;
@@ -975,29 +963,7 @@ ParallelApplyWorkerMain(Datum main_arg)
 	set_apply_error_context_origin(originname);
 
 	LogicalParallelApplyLoop(mqh);
-
-	/*
-	 * The parallel apply worker must not get here because the parallel apply
-	 * worker will only stop when it receives a SIGTERM or SIGUSR2 from the
-	 * leader, or SIGINT from itself, or when there is an error. None of these
-	 * cases will allow the code to reach here.
-	 */
-	Assert(false);
-}
-
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	Assert(false);				/* LogicalParallelApplyLoop never returns */
 }
 
 /*
@@ -1064,7 +1030,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1074,6 +1040,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1097,8 +1066,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1189,14 +1156,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();
 		}
 
@@ -1261,13 +1229,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 e6112e11ec2..d66ba0b6f5a 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -58,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;
@@ -183,7 +184,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -219,27 +219,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -473,6 +474,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
@@ -539,7 +541,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))
@@ -566,7 +567,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -589,13 +590,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -616,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -630,13 +632,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -663,7 +666,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);
@@ -672,8 +675,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -710,13 +714,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
@@ -738,7 +742,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.
  */
@@ -747,7 +751,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -815,7 +819,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);
@@ -847,6 +851,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;
 }
 
@@ -858,7 +863,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;
 }
 
 /*
@@ -1019,7 +1024,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);
@@ -1045,6 +1049,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1193,8 +1198,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);
 }
 
 /*
@@ -1208,13 +1217,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).
@@ -1256,6 +1268,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)
 		{
@@ -1416,21 +1429,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);
 		}
 	}
 
@@ -1585,7 +1596,7 @@ CreateConflictDetectionSlot(void)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 9c92fddd624..67750242f7c 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 5e1ee7e4728..9dbf89cb832 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,9 +59,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -79,9 +80,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -104,7 +105,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1223,7 +1224,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;
@@ -1286,13 +1287,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1314,7 +1316,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1358,7 +1362,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
@@ -1404,13 +1408,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1418,7 +1425,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);
 
@@ -1430,16 +1437,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);
 
@@ -1454,7 +1461,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;
@@ -1529,19 +1536,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1681,7 +1684,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);
 
@@ -1720,7 +1723,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1737,7 +1740,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1745,8 +1748,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1754,13 +1757,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1845,7 +1849,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1923,7 +1927,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index 2f2f0121ecf..f85bbd67b3b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -167,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;
@@ -218,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;
@@ -518,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.
 						 */
@@ -697,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 bae8c011390..8c31bcad43e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -4055,11 +4056,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();
@@ -4178,11 +4176,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4203,23 +4201,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5928,8 +5925,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 28c7019402b..9742ddd1fd4 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -622,7 +622,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	int			active_pid;
 
 	Assert(name != NULL);
 
@@ -685,7 +684,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -707,7 +705,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -1968,7 +1966,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1978,7 +1976,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		int			active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2062,11 +2059,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2105,22 +2097,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2161,7 +2156,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3253,11 +3249,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))
@@ -3267,6 +3260,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a way to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index d1582a5d711..4ba5ed828bf 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -265,22 +266,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -294,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))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -314,9 +315,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.")));
@@ -325,11 +325,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,
@@ -337,7 +341,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -944,7 +948,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 ec6c516530d..ed53d96c315 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -246,16 +247,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
-	/* Properly accept or ignore signals the postmaster might send us */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -439,9 +432,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();
@@ -514,25 +506,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -680,7 +674,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -712,8 +706,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)
@@ -1384,7 +1382,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index e62e8a20420..933c4e41557 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"
@@ -332,7 +333,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 5acc8ee3ac8..446eafe694f 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -201,15 +202,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_STOPPING = false;
-
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM.
+ * When set, the main loop is responsible for checking
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -282,7 +280,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -370,7 +368,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -738,8 +737,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();
@@ -773,7 +780,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)
@@ -972,12 +981,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1036,9 +1047,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1475,7 +1486,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1529,7 +1540,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1619,12 +1630,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();
 
@@ -1664,23 +1671,27 @@ ProcessPendingWrites(void)
 
 		/* Sleep until something happens or we time out */
 		WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime,
-				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
+				   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+				   CheckForInterruptsMask |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1783,7 +1794,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1870,12 +1881,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();
@@ -1885,7 +1896,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			XLogBackgroundFlush();
 
 		/*
@@ -1910,7 +1921,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1992,11 +2003,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2022,7 +2037,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2895,6 +2910,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2909,13 +2925,20 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
 
-		CHECK_FOR_INTERRUPTS();
+		/* Clear any already-pending wakeups */
+		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();
@@ -2964,13 +2987,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);
 		}
 
@@ -3022,7 +3045,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3060,6 +3088,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3116,6 +3145,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3210,7 +3240,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3579,8 +3609,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3697,52 +3727,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3825,24 +3846,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SetLatch(), helping
-	 * walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3890,16 +3915,16 @@ WalSndInitStopping(void)
 	for (i = 0; i < max_wal_senders; i++)
 	{
 		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
-		pid_t		pid;
+		ProcNumber	procno;
 
 		SpinLockAcquire(&walsnd->mutex);
-		pid = walsnd->pid;
+		procno = walsnd->pgprocno;
 		SpinLockRelease(&walsnd->mutex);
 
-		if (pid == 0)
+		if (procno == INVALID_PROC_NUMBER)
 			continue;
 
-		SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, INVALID_PROC_NUMBER);
+		SendInterrupt(INTERRUPT_WALSND_INIT_STOPPING, procno);
 	}
 }
 
diff --git a/src/backend/storage/aio/aio.c b/src/backend/storage/aio/aio.c
index e4ae3031fef..da2601e8b92 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d9617c20e76..4c21b0f48a3 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,12 +262,12 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
 			pgaio_debug_io(DEBUG4, staged_ios[i],
 						   "choosing worker %d",
@@ -277,8 +276,10 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -392,20 +393,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -453,11 +454,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -490,13 +491,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -567,18 +568,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d1babaff023..cb61e6e1fce 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3345,7 +3346,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);
@@ -3525,7 +3526,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3606,7 +3607,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3697,7 +3698,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);
 	}
@@ -6643,12 +6644,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other signals as well. We
+		 * take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e208457df27..367c1168216 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -357,8 +357,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	/*
 	 * Block all blockable signals, except SIGQUIT.  posix_fallocate() can run
 	 * for quite a long time, and is an all-or-nothing operation.  If we
-	 * allowed SIGUSR1 to interrupt us repeatedly (for example, due to
-	 * recovery conflicts), the retry loop might never succeed.
+	 * allowed signals to interrupt us repeatedly (for example, due to config
+	 * file reloads), the retry loop might never succeed.
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..806947571ae 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -172,13 +173,12 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests; we're doing our best to
-	 * close up shop already.  Note that the signal handlers will not set
-	 * these flags again, now that proc_exit_inprogress is set.
+	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
+	 * to prevent any more interrupts from being processed; we're doing our
+	 * best to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 40312df2cac..b34e2cb731e 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"
@@ -3450,27 +3451,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3510,10 +3511,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;
 		}
@@ -3545,21 +3546,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 7505c9d3a37..d17a8cc4e4f 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -27,7 +28,7 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -35,28 +36,21 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
  * (or greater) appears in the pss_barrierGeneration flag of every process,
  * we know that the message has been received everywhere.
@@ -66,7 +60,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) */
@@ -106,7 +99,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -151,7 +143,6 @@ ProcSignalShmemInit(void)
 			SpinLockInit(&slot->pss_mutex);
 			pg_atomic_init_u32(&slot->pss_pid, 0);
 			slot->pss_cancel_key_len = 0;
-			MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
 			pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
 			pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
 			ConditionVariableInit(&slot->pss_barrierCV);
@@ -182,9 +173,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -233,9 +221,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;
@@ -270,73 +259,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
@@ -381,8 +303,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
@@ -393,25 +315,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;
@@ -471,23 +380,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.
  *
@@ -503,12 +395,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
@@ -640,68 +528,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -760,6 +587,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 3ce6068ac54..ab995093140 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * process interrupts are used for synchronization.  Only the sender may send,
  * and only the receiver may receive.  This is intended to allow a user
  * backend to communicate with worker backends that it has registered.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -44,10 +45,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.
@@ -112,7 +114,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
@@ -214,7 +216,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));
 }
 
 /*
@@ -232,7 +234,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));
 }
 
 /*
@@ -341,16 +343,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
@@ -539,7 +540,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;
 	}
 
@@ -557,16 +558,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)
@@ -619,8 +619,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)
 	{
@@ -895,7 +895,7 @@ shm_mq_detach_internal(shm_mq *mq)
 	SpinLockRelease(&mq->mq_mutex);
 
 	if (victim != NULL)
-		SetLatch(&victim->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(victim));
 }
 
 /*
@@ -993,7 +993,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
@@ -1001,7 +1001,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;
@@ -1009,17 +1009,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();
@@ -1054,7 +1055,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.
 			 */
@@ -1150,25 +1151,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);
 	}
 }
 
@@ -1250,14 +1255,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;
@@ -1293,7 +1302,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 d48b4fe3799..aaeebca6c91 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -41,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
@@ -201,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..dcabd25956a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and will set the
- * processes latch. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * setting the process's latch.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,15 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
-		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
-		 * can just call AcceptInvalidationMessages() to do this.  If we
-		 * aren't, we start and immediately end a transaction; the call to
-		 * AcceptInvalidationMessages() happens down inside transaction start.
+		 * run, which will do the necessary work.  If we are inside a
+		 * transaction we can just call AcceptInvalidationMessages() to do
+		 * this.  If we aren't, we start and immediately end a transaction;
+		 * the call to AcceptInvalidationMessages() happens down inside
+		 * transaction start.
 		 *
 		 * It is awfully tempting to just call AcceptInvalidationMessages()
 		 * without the rest of the xact start/stop overhead, and I think that
@@ -190,14 +148,20 @@ ProcessCatchupInterrupt(void)
 		 */
 		if (IsTransactionOrTransactionBlock())
 		{
-			elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt inside transaction");
 			AcceptInvalidationMessages();
 		}
 		else
 		{
-			elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt outside transaction");
 			StartTransactionCommand();
 			CommitTransactionCommand();
 		}
+
+		/*
+		 * If another catchup interrupt arrived while we were procesing the
+		 * previous one, process the new one too.
+		 */
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -658,8 +666,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -670,8 +678,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index d83afbfb9d6..c9242d6fb89 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -596,7 +597,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
@@ -698,7 +699,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
@@ -746,7 +751,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:
@@ -766,15 +775,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
@@ -838,9 +848,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);
@@ -936,6 +956,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -945,6 +966,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -954,6 +976,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 772e350a0c0..612de8b35e9 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -128,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,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -183,9 +184,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
 
@@ -235,7 +243,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)
@@ -285,12 +293,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");
@@ -311,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -349,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -412,7 +426,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;
 
@@ -496,9 +511,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)
 		{
@@ -535,7 +550,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
@@ -553,10 +568,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.
@@ -566,7 +577,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, InterruptMask interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +591,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 */
@@ -608,10 +616,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)
@@ -645,14 +653,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)
@@ -682,40 +690,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +747,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)
@@ -794,9 +795,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)
@@ -858,9 +858,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;
@@ -886,8 +886,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 |
@@ -902,10 +901,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
 	{
@@ -985,10 +984,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)
 	{
@@ -1044,6 +1046,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);
 
@@ -1065,100 +1068,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		InterruptMask old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
+		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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1229,16 +1249,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1391,13 +1411,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1513,16 +1533,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1621,6 +1641,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
@@ -1726,19 +1755,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1783,7 +1808,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
@@ -1889,18 +1914,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)
 {
@@ -1917,7 +1941,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;
@@ -2004,7 +2028,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2014,12 +2037,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();
@@ -2027,12 +2049,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..f961054a4ba 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1584,7 +1585,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;
@@ -3617,7 +3622,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 ba7eaed1a13..44809fea210 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -214,6 +215,8 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -301,14 +304,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < MaxBackends + NUM_AUXILIARY_PROCS)
 		{
+#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);
 		}
 
@@ -518,13 +535,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);
@@ -688,13 +700,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -717,6 +724,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.
@@ -986,21 +997,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1059,7 +1069,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 */
@@ -1078,11 +1088,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);
 
@@ -1410,18 +1429,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
 	{
@@ -1467,9 +1486,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)
 			{
@@ -1675,8 +1695,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)
 	{
@@ -1713,7 +1733,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 links invalid.
  *
@@ -1742,7 +1762,7 @@ ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus)
 	pg_atomic_write_u64(&MyProc->waitStart, 0);
 
 	/* And awaken it */
-	SetLatch(&proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -1899,14 +1919,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;
 }
 
@@ -1985,34 +2003,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 378c2a03f39..dd8b1fc10f5 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 b1accc68b95..fbed7d82e98 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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 c517115927c..9595476c8e0 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -198,6 +198,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
 
+	/*
+	 * FIXME: install INTERRUPT_TERMINATE handler at least? Not really needed
+	 * because no one else can send us interrupts yet, and we don't raise them
+	 * within the backend yet either. But if we changed postmaster to send
+	 * INTERRUPT_TERMINATE directly, for example, we'd need to handle it
+	 * already.
+	 */
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5d66c80567c..5e595e51987 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -335,8 +336,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -353,11 +352,23 @@ SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
+
+	/*
+	 * Don't allow query cancel interrupts while reading input from the
+	 * client, because we might lose sync in the FE/BE protocol.  (Die
+	 * interrupts are OK, because we won't read any further messages from the
+	 * client in that case.)
+	 *
+	 * See similar logic in ProcessRecoveryConflictInterrupt().
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Get message type code from the frontend.
 	 */
-	HOLD_CANCEL_INTERRUPTS();
 	pq_startmsgread();
 	qtype = pq_getbyte();
 
@@ -464,7 +475,9 @@ SocketBackend(StringInfo inBuf)
 	 */
 	if (pq_getmessage(inBuf, maxmsglen))
 		return EOF;				/* suitable message already logged */
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	return qtype;
 }
@@ -488,104 +501,6 @@ ReadCommand(StringInfo inBuf)
 	return result;
 }
 
-/*
- * ProcessClientReadInterrupt() - Process interrupts specific to client reads
- *
- * This is called just before and after low-level reads.
- * 'blocked' is true if no data was available to read and we plan to retry,
- * false if about to read or done reading.
- *
- * Must preserve errno!
- */
-void
-ProcessClientReadInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (DoingCommandRead)
-	{
-		/* Check for general interrupts that arrived before/while reading */
-		CHECK_FOR_INTERRUPTS();
-
-		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
-			ProcessCatchupInterrupt();
-
-		/* Process notify interrupts, if any */
-		if (notifyInterruptPending)
-			ProcessNotifyInterrupt(true);
-	}
-	else if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If there is no data available to read, then it's safe
-		 * (and sane) to handle that now.  If we haven't tried to read yet,
-		 * make sure the process latch is set, so that if there is no data
-		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while reading.
-		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
-/*
- * ProcessClientWriteInterrupt() - Process interrupts specific to client writes
- *
- * This is called just before and after low-level writes.
- * 'blocked' is true if no data could be written and we plan to retry,
- * false if about to write or done writing.
- *
- * Must preserve errno!
- */
-void
-ProcessClientWriteInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If it's not possible to write, then we should handle
-		 * that immediately, else a stuck client could indefinitely delay our
-		 * response to the signal.  If we haven't tried to write yet, make
-		 * sure the process latch is set, so that if the write would block
-		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while writing.
-		 */
-		if (blocked)
-		{
-			/*
-			 * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't
-			 * service ProcDiePending.
-			 */
-			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-			{
-				/*
-				 * We don't want to send the client the error message, as a)
-				 * that would possibly block again, and b) it would likely
-				 * lead to loss of protocol sync because we may have already
-				 * sent a partial protocol message.
-				 */
-				if (whereToSendOutput == DestRemote)
-					whereToSendOutput = DestNone;
-
-				CHECK_FOR_INTERRUPTS();
-			}
-		}
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
 /*
  * Do raw parsing (only).
  *
@@ -2910,7 +2825,7 @@ drop_unnamed_stmt(void)
  * Either some backend has bought the farm, or we've been told to shut down
  * "immediately"; so we need to stop what we're doing and exit.
  */
-void
+static void
 quickdie(SIGNAL_ARGS)
 {
 	sigaddset(&BlockSig, SIGQUIT);	/* prevent nested calls */
@@ -3007,50 +2922,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-void
-StatementCancelHandler(SIGNAL_ARGS)
-{
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3067,22 +2958,91 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
- * in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	/*
+	 * Check the conflicts one by one, clearing each flag only before
+	 * processing the particular conflict.  This ensures that if multiple
+	 * conflicts are pending, we come back here to process the remaining
+	 * conflicts, if an error is thrown during processing one of them.
+	 */
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
+		 reason++)
+	{
+		if ((pending & (1 << reason)) != 0)
+		{
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
+			ProcessRecoveryConflict(reason);
+		}
+	}
+}
+
+void
+ProcessRecoveryConflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
@@ -3242,14 +3202,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,72 +3240,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
-
-	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
-	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * Check the conflicts one by one, clearing each flag only before
-	 * processing the particular conflict.  This ensures that if multiple
-	 * conflicts are pending, we come back here to process the remaining
-	 * conflicts, if an error is thrown during processing one of them.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
- *
- * If an interrupt condition is pending, and it's safe to service it,
- * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
- *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts
- * is guaranteed to clear the InterruptPending flag before returning.
- * (This is not the same as guaranteeing that it's still clear when we
- * return; another interrupt could have arrived.  But we promise that
- * any pre-existing one will have been serviced.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3394,65 +3313,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		CheckClientConnectionPending = false;
-
-		/*
-		 * Check for lost connection and re-arm, if still configured, but not
-		 * if we've arrived back at DoingCommandRead state.  We don't want to
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * Check for lost connection and re-arm, if still configured, but not if
+	 * we've arrived back at DoingCommandRead state.  We don't want to wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
+
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
 
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
+
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
-	{
-		/*
-		 * Re-arm InterruptPending so that we process the cancel request as
-		 * soon as we're done reading the message.  (XXX this is seriously
-		 * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we
-		 * can't use that macro directly as the initial test in this function,
-		 * meaning that this code also creates opportunities for other bugs to
-		 * appear.)
-		 */
-		InterruptPending = true;
-	}
-	else if (QueryCancelPending)
+
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3505,76 +3414,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * interrupt.  We need to unset the flag before the injection point,
-		 * otherwise we could loop in interrupts checking.
-		 */
-		IdleInTransactionSessionTimeoutPending = false;
-		if (IdleInTransactionSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4238,9 +4137,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4253,13 +4156,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4273,7 +4188,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4282,11 +4204,31 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4454,11 +4396,14 @@ PostgresMain(const char *dbname, const char *username)
 		 * forgetting a timeout cancel.
 		 */
 		disable_all_timeouts(false);	/* do first to avoid race condition */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 		idle_in_transaction_timeout_enabled = false;
 		idle_session_timeout_enabled = false;
 
 		/* Not reading from the client anymore. */
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 		DoingCommandRead = false;
 
 		/* Make sure libpq is in a good state */
@@ -4639,7 +4584,7 @@ PostgresMain(const char *dbname, const char *username)
 				 * were received during the just-finished transaction, they'll
 				 * be seen by the client before ReadyForQuery is.
 				 */
-				if (notifyInterruptPending)
+				if (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4728,6 +4673,28 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		DoingCommandRead = true;
 
+		/*
+		 * Let cache inval catchup requests to be processed while we're idle.
+		 */
+		EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+
+		/*
+		 * When we're truly idle, i.e. no transaction in progress, we can
+		 * additionally process any async LISTEN/NOTIFY messages and stats
+		 * update requests.
+		 */
+		if (!IsTransactionOrTransactionBlock())
+		{
+			EnableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+			EnableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+		}
+
+		/*
+		 * If any of the newly-enabled interrupts are already pending, process
+		 * them now.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * (3) read a command (loop blocks here)
 		 */
@@ -4757,22 +4724,22 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 87070235d11..6aa69805891 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -295,14 +295,7 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 	}
 
-	procNumber = GetNumberFromPGProc(proc);
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
+	SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc));
 
 	PG_RETURN_BOOL(true);
 }
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 32a787d7df7..9f660363921 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,16 +346,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;
 
@@ -374,11 +374,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 e2603183f1c..c49bb59ad9e 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1739,10 +1739,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 cb1c9d85ffe..becfc62fd32 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -527,7 +527,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,6 @@
 
 ProtocolVersion FrontendProtocol;
 
-volatile sig_atomic_t InterruptPending = false;
-volatile sig_atomic_t QueryCancelPending = false;
-volatile sig_atomic_t ProcDiePending = false;
-volatile sig_atomic_t CheckClientConnectionPending = false;
-volatile sig_atomic_t ClientConnectionLost = false;
-volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t TransactionTimeoutPending = false;
-volatile sig_atomic_t IdleSessionTimeoutPending = false;
-volatile sig_atomic_t ProcSignalBarrierPending = false;
-volatile sig_atomic_t LogMemoryContextPending = false;
-volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 QueryCancelHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
 
 int			MyProcPid;
 pg_time_t	MyStartTime;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fadf856d9bc..fdcee946405 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -143,25 +139,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -201,10 +184,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.
@@ -225,48 +207,6 @@ InitStandaloneProcess(const char *argv0)
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
 
-void
-SwitchToSharedLatch(void)
-{
-	Assert(MyLatch == &LocalLatchData);
-	Assert(MyProc != NULL);
-
-	MyLatch = &MyProc->procLatch;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	/*
-	 * Set the shared latch as the local one might have been set. This
-	 * shouldn't normally be necessary as code is supposed to check the
-	 * condition before waiting for the latch, but a bit care can't hurt.
-	 */
-	SetLatch(MyLatch);
-}
-
-void
-InitProcessLocalLatch(void)
-{
-	MyLatch = &LocalLatchData;
-	InitLatch(MyLatch);
-}
-
-void
-SwitchBackToLocalLatch(void)
-{
-	Assert(MyLatch != &LocalLatchData);
-	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);
-
-	MyLatch = &LocalLatchData;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	SetLatch(MyLatch);
-}
-
 /*
  * Return a human-readable string representation of a BackendType.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..4f1aa94a661 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -1371,20 +1372,19 @@ ShutdownPostgres(int code, Datum arg)
 static void
 StatementTimeoutHandler(void)
 {
-	int			sig = SIGINT;
-
 	/*
 	 * During authentication the timeout is used to deal with
 	 * authentication_timeout - we want to quit in response to such timeouts.
 	 */
 	if (ClientAuthInProgress)
-		sig = SIGTERM;
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+	else
+		RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, sig);
-#endif
-	kill(MyProcPid, sig);
+	/*
+	 * FIXME: we used to signal the whole process group. Is that important, if
+	 * we're e.g. executing archive_command ?
+	 */
 }
 
 /*
@@ -1393,51 +1393,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
- * to call this function if we see LogMemoryContextPending set.
- * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
- * the target process for logging of memory contexts is a backend.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/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 01bdf2bec1f..21b0b0a1ec7 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -53,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;
 
@@ -70,7 +69,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..239d58f7597
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,370 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ */
+#include "storage/waiteventset.h"
+
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 63
+
+/*
+ * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+ * waiting for an interrupt.  If set, the backend needs to be woken up when a
+ * bit in the pending interrupts mask is set.  It's used internally by the
+ * interrupt machinery, and cannot be used directly in the public functions.
+ * It's named differently to distinguish it from the actual interrupt flags.
+ */
+#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
+
+extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 412bc9758fb..35fbff9a763 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -18,7 +18,7 @@
 
 #include "lib/stringinfo.h"
 #include "libpq/libpq-be.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 
 
 /*
@@ -61,7 +61,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 c62fffb5998..3ba2b82b3a8 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -17,7 +17,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/meson.build b/src/include/meson.build
index 7d734d92dab..da8129355cc 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -135,6 +135,7 @@ header_subdirs = [
   'executor',
   'fe_utils',
   'foreign',
+  'ipc',
   'jit',
   'lib',
   'libpq',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..476e60a4d06 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,132 +28,15 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
-
-#define InvalidPid				(-1)
-
-
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/interrupt.h"
 #endif
 
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
+#define InvalidPid				(-1)
 
 
 /*****************************************************************************
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
-
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TableSyncWorkerMain(Datum main_arg);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if
+								 * not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4ecbdcfadac..d368bc60536 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index fbdadc86959..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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 */
-
-/*
- * 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 1f1ab7d02ca..00ea9f86422 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -18,7 +18,6 @@
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -190,9 +189,6 @@ struct PGPROC
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 	ProcWaitStatus waitStatus;
 
-	Latch		procLatch;		/* generic latch for process */
-
-
 	TransactionId xid;			/* id of top-level transaction currently being
 								 * executed by this proc, if running and XID
 								 * is assigned; else InvalidTransactionId.
@@ -236,16 +232,6 @@ struct PGPROC
 
 	BackendType backendType;	/* what kind of process is this? */
 
-	/*
-	 * While in hot standby mode, shows that a conflict signal has been sent
-	 * for the current transaction. Set/cleared while holding ProcArrayLock,
-	 * though not required. Accessed without lock, if needed.
-	 *
-	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
-	 * enum value.
-	 */
-	pg_atomic_uint32 pendingRecoveryConflicts;
-
 	/*
 	 * Info about LWLock the process is currently waiting for, if any.
 	 *
@@ -337,6 +323,24 @@ struct PGPROC
 	PGPROC	   *lockGroupLeader;	/* lock group leader, if I'm a member */
 	dlist_head	lockGroupMembers;	/* list of members, if I'm a leader */
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
+
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint64 pendingInterrupts;
+
+	/*
+	 * While in hot standby mode, shows that a conflict signal has been sent
+	 * for the current transaction. Set/cleared while holding ProcArrayLock,
+	 * though not required. Accessed without lock, if needed.
+	 *
+	 * This is a bitmask; each bit corresponds to a RecoveryConflictReason
+	 * enum value.
+	 */
+	pg_atomic_uint32 pendingRecoveryConflicts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE		interruptWakeupEvent;
+#endif
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -451,6 +455,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;
@@ -533,9 +539,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..a7f974c5ba2 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,17 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +73,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
+
+struct ResourceOwnerData;
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  struct Latch *latch, void *user_data);
+							  uint64 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint64 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 11ab1717a16..5d47643eb77 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 4c31e503eb1..0bd50bdb23f 100644
--- a/src/port/pgsleep.c
+++ b/src/port/pgsleep.c
@@ -32,10 +32,10 @@
  *
  * CAUTION: It's not a good idea to use long sleeps in the backend.  They will
  * silently return early if a signal is caught, but that doesn't include
- * latches being set on most OSes, and even signal handlers that set MyLatch
- * might happen to run before the sleep begins, allowing the full delay.
- * Better practice is to use WaitLatch() with a timeout, so that backends
- * respond to latches and signals promptly.
+ * interrupts being received on most OSes, and even signal handlers that raise
+ * an interrupt might happen to run before the sleep begins, allowing the full
+ * delay.  Better practice is to use WaitInterrupt() with a timeout, so that
+ * backends respond to interrupts and signals promptly.
  */
 void
 pg_usleep(long microsec)
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index a0aec04d994..777d00283da 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 579e5933d28..e6169f97a9e 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -133,6 +134,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;
@@ -222,8 +224,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)
@@ -264,6 +264,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;
@@ -286,11 +289,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 fe1794c6077..d8675ac1423 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "varatt.h"
 
@@ -170,6 +170,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
@@ -234,13 +236,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index 6a4147554bb..2e7882a12d1 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,7 +19,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -52,7 +52,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();
@@ -116,13 +115,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 d1e4a2bd952..8f9fa9e1834 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -155,11 +153,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -213,26 +210,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
@@ -366,7 +362,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.
@@ -420,8 +415,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 241945734ec..9a5d8ac10e9 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1327,6 +1327,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1564,7 +1565,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2028,6 +2028,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.47.3



  [text/x-patch] v11-0004-Introduce-PendingInterrupts-with-separate-flags-.patch (53.6K, ../../[email protected]/5-v11-0004-Introduce-PendingInterrupts-with-separate-flags-.patch)
  download | inline diff:
From 8927e695a56e15d412c33915de11b3a4610b4762 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 20 Feb 2026 16:14:09 +0200
Subject: [PATCH v11 4/4] Introduce PendingInterrupts with separate 'flags'
 field and "attention"

This does a couple of things:

- Split the interrupts mask into two 32-bit fields on
  PG_HAVE_ATOMIC_U64_SIMULATION systems. This fixes the self-deadlock
  risk when used from signal handlers.

- Introduce an "attention mask" field, where the backend can advertise
  the interrupts it is currently interested in, and a separate "flags"
  field. We no longer need to steal bits from the interrupt mask for
  the flags.

  Whenever a backend sends an interrupt, it checks the attention mask
  and only wakes up the backend if the interrupt is in the attention
  mask. When not sleeping, the same mechanism is used to set a flag
  for CHECK_FOR_INTERRUPTS(), telling the next CHECK_FOR_INTERRUPTS()
  that it has some work to do. By moving that work of checking the
  attention mask to the sender, CHECK_FOR_INTERRUPTS() needs fewer
  instructions.
---
 src/backend/access/spgist/spgdoinsert.c |   9 +-
 src/backend/access/transam/parallel.c   |   2 +-
 src/backend/ipc/interrupt.c             | 317 +++++++++++++++-----
 src/backend/storage/ipc/ipc.c           |   9 +-
 src/backend/storage/ipc/waiteventset.c  |  58 ++--
 src/backend/tcop/postgres.c             |   5 +-
 src/backend/utils/error/elog.c          |   4 +-
 src/include/ipc/interrupt.h             | 383 ++++++++++--------------
 src/include/ipc/standard_interrupts.h   | 194 ++++++++++++
 src/include/storage/proc.h              |   2 +-
 src/tools/pgindent/typedefs.list        |   1 +
 11 files changed, 634 insertions(+), 350 deletions(-)
 create mode 100644 src/include/ipc/standard_interrupts.h

diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index b8b5eaac57a..7c7371c69e8 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,11 +2035,8 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
-		 *
-		 * FIXME: CheckForInterruptsMask covers more than just query cancel
-		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
+		if (INTERRUPTS_PENDING_CONDITION())
 		{
 			result = false;
 			break;
@@ -2165,7 +2162,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
+			if (INTERRUPTS_PENDING_CONDITION())
 			{
 				result = false;
 				break;
@@ -2341,7 +2338,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
+	Assert(INTERRUPTS_CAN_BE_PROCESSED());
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 65c467f25f9..4853fc01fb0 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -237,7 +237,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
+	if (!INTERRUPTS_CAN_BE_PROCESSED())
 		pcxt->nworkers = 0;
 
 	/*
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
index 61d148409bc..cf901eb1d9d 100644
--- a/src/backend/ipc/interrupt.c
+++ b/src/backend/ipc/interrupt.c
@@ -22,29 +22,18 @@
 #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];
 
-/*
- * XXX: is 'volatile' still needed on all the variables below? Which ones are
- * accessed from signal handlers?
- */
-
 /* Bitmask of currently enabled interrupts */
-volatile InterruptMask EnabledInterruptsMask;
-
-/*
- * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
- * equal to EnabledInterruptsMask, except when interrupts are held off by
- * HOLD/RESUME_INTERRUPTS() or a critical section.
- */
-volatile InterruptMask CheckForInterruptsMask;
-
-/* Variables for holdoff mechanism */
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
+InterruptMask EnabledInterruptsMask;
 
 /* A common WaitEventSet used to implement WaitInterrupt() */
 static WaitEventSet *InterruptWaitSet;
@@ -53,9 +42,10 @@ static WaitEventSet *InterruptWaitSet;
 #define InterruptWaitSetInterruptPos 0
 #define InterruptWaitSetPostmasterDeathPos 1
 
-static pg_atomic_uint64 LocalPendingInterrupts;
-
-pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+static PendingInterrupts LocalPendingInterrupts;
+PendingInterrupts *MyPendingInterrupts = &LocalPendingInterrupts;
+pg_atomic_uint32 *MyPendingInterruptsFlags = &LocalPendingInterrupts.flags;
+const pg_atomic_uint32 ZeroPendingInterruptsFlags;
 
 static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
 
@@ -94,10 +84,8 @@ EnableInterrupt(InterruptMask interruptMask)
 			Assert(interrupt_handlers[i] != NULL);
 	}
 #endif
-
 	EnabledInterruptsMask |= interruptMask;
-	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
+	SetInterruptAttentionMask(EnabledInterruptsMask);
 }
 
 /*
@@ -112,67 +100,173 @@ 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)
-		CheckForInterruptsMask = EnabledInterruptsMask;
+		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.
+ * If an interrupt condition is pending, and it's safe to service it, then
+ * clear the flag and call the interrupt handler.
  *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
- * ProcessInterrupts is guaranteed to clear the given interrupt before
- * returning, if it was set when entering.  (This is not the same as
- * guaranteeing that it's still clear when we return; another interrupt could
- * have arrived.  But we promise that any pre-existing one will have been
- * serviced.)
+ * 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(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+	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 */
-	interruptsToProcess =
-		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_hi) << 32;
+#endif
+	interruptsToProcess = pending & EnabledInterruptsMask;
 
-	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	if (interruptsToProcess != 0)
 	{
-		if ((interruptsToProcess & UINT64_BIT(i)) != 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++)
 		{
-			/*
-			 * 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]) ();
+			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().
+	 */
 }
 
 /*
- * Switch to local interrupts.  Other backends can't send interrupts to this
- * one.  Only RaiseInterrupt() can set them, from inside this process.
+ * 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
-SwitchToLocalInterrupts(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)
 {
-	if (MyPendingInterrupts == &LocalPendingInterrupts)
+	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 = &LocalPendingInterrupts;
+	MyPendingInterrupts = new_ptr;
+	if (MyPendingInterruptsFlags == &old_ptr->flags)
+		MyPendingInterruptsFlags = &new_ptr->flags;
 
 	/*
 	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
@@ -181,14 +275,42 @@ SwitchToLocalInterrupts(void)
 	pg_memory_barrier();
 
 	/*
-	 * Mix in the interrupts that we have received already in our shared
-	 * interrupt vector, while atomically clearing it.  Other backends may
-	 * continue to set bits in it after this point, but we've atomically
+	 * 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.
 	 */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	{
+		uint64		old_interrupts;
+
+		old_interrupts = pg_atomic_exchange_u64(&old_ptr->interrupts, 0);
+		pg_atomic_fetch_or_u64(&new_ptr->interrupts, old_interrupts);
+	}
+#else
+	{
+		uint32		old_interrupts_lo;
+		uint32		old_interrupts_hi;
+
+		old_interrupts_lo = pg_atomic_exchange_u32(&old_ptr->interrupts_lo, 0);
+		old_interrupts_hi = pg_atomic_exchange_u32(&old_ptr->interrupts_hi, 0);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_lo, old_interrupts_lo);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_hi, old_interrupts_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);
 }
 
 /*
@@ -198,20 +320,61 @@ SwitchToLocalInterrupts(void)
 void
 SwitchToSharedInterrupts(void)
 {
-	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
-		return;
+	SwitchMyPendingInterruptsPtr(&MyProc->pendingInterrupts);
+}
 
-	MyPendingInterrupts = &MyProc->pendingInterrupts;
+static bool
+SendOrRaiseInterrupt(PendingInterrupts *ptr, InterruptMask interruptMask)
+{
+	uint64		old_pending;
+	uint64		attention_mask;
+	uint32		old_flags;
+	bool		wakeup = false;
 
 	/*
-	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
-	 * seeing the new MyPendingInterrupts destination.
+	 * Do an "unlocked" read first, for a quick exit if all the bits are
+	 * already set.
 	 */
-	pg_memory_barrier();
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	old_pending = pg_atomic_read_u64(&ptr->interrupts);
+#else
+	old_pending = (uint64) pg_atomic_read_u32(&ptr->interrupts_lo);
+	old_pending |= (uint64) pg_atomic_read_u32(&ptr->interrupts_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->interrupts, interruptMask);
+#else
+	(void) pg_atomic_fetch_or_u32(&ptr->interrupts_lo, (uint32) interruptMask);
+	(void) pg_atomic_fetch_or_u32(&ptr->interrupts_hi, (uint32) (interruptMask >> 32));
+#endif
 
-	/* Mix in any unhandled bits from LocalPendingInterrupts. */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+	/*
+	 * 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;
 }
 
 /*
@@ -223,15 +386,7 @@ SwitchToSharedInterrupts(void)
 void
 RaiseInterrupt(InterruptMask interruptMask)
 {
-	uint64		old_pending;
-
-	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
-
-	/*
-	 * If the process is currently blocked waiting for an interrupt to arrive,
-	 * and the interrupt wasn't already pending, wake it up.
-	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(MyPendingInterrupts, interruptMask))
 		WakeupMyProc();
 }
 
@@ -248,14 +403,12 @@ void
 SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 {
 	PGPROC	   *proc;
-	uint64		old_pending;
 
 	Assert(pgprocno != INVALID_PROC_NUMBER);
 	Assert(pgprocno >= 0);
 	Assert(pgprocno < ProcGlobal->allProcCount);
 
 	proc = &ProcGlobal->allProcs[pgprocno];
-	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
 
 	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
 
@@ -263,7 +416,7 @@ SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 	 * If the process is currently blocked waiting for an interrupt to arrive,
 	 * and the interrupt wasn't already pending, wake it up.
 	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(&proc->pendingInterrupts, interruptMask))
 		WakeupOtherProc(proc);
 }
 
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 806947571ae..0e86491e1b6 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -173,14 +173,13 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
-	 * to prevent any more interrupts from being processed; we're doing our
-	 * best to close up shop already.
+	 * 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.
 	 */
 	ClearInterrupt(INTERRUPT_TERMINATE);
 	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
-	InterruptHoldoffCount = 1;
-	CritSectionCount = 0;
+	ResetInterruptHoldoffCounts(1, 0);
 
 	/*
 	 * Also clear the error context stack, to prevent error callbacks from
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 612de8b35e9..bd52110389d 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -714,8 +714,7 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, InterruptMask interru
 		 * changes.  It means that when interrupt mask is set to 0, we will
 		 * listen on the kernel object unnecessarily, and might get some
 		 * spurious wakeups. The interrupt sending code should not wake us up
-		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
-		 * should be rare.
+		 * if the SLEEPING bit is not armed, though, so it should be rare.
 		 */
 		return;
 	}
@@ -1072,6 +1071,14 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	pgwin32_dispatch_queued_signals();
 #endif
 
+	/*
+	 * 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
@@ -1084,28 +1091,35 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	 */
 	if (set->interrupt_mask != 0)
 	{
-		InterruptMask old_mask;
 		bool		already_pending = false;
 
 		/*
 		 * Perform a plain atomic read first as a fast path for the case that
 		 * an interrupt is already pending.
 		 */
-		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
-		already_pending = ((old_mask & set->interrupt_mask) != 0);
+		already_pending = InterruptPending(set->interrupt_mask);
 
 		if (!already_pending)
 		{
 			/*
-			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
-			 * an interrupt is already pending. The atomic op provides
-			 * synchronization so that if an interrupt bit is set after this,
-			 * the setter will wake us up.
+			 * 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.
 			 */
-			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
-			already_pending = ((old_mask & set->interrupt_mask) != 0);
+#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();
+			already_pending = InterruptPending(set->interrupt_mask);
 
-			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			/* Remember to clear the SLEEPING flag afterwards. */
 			sleeping_flag_armed = true;
 		}
 
@@ -1175,9 +1189,17 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 		}
 	}
 
-	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	/*
+	 * If we slept, clear the SLEEPING flag again and reset the attention mask
+	 * for CHECK_FOR_INTERRUPTS()
+	 */
 	if (sleeping_flag_armed)
-		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+	{
+		pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+		SetInterruptAttentionMask(EnabledInterruptsMask);
+	}
+
+	RESUME_INTERRUPTS();
 
 #ifndef WIN32
 	waiting = false;
@@ -1255,7 +1277,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1414,7 +1436,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1539,7 +1561,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1760,7 +1782,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5e595e51987..94e59aff4ea 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3202,11 +3202,11 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
+			if ((EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) == 0)
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
-				 * code in ProcessInterrupts().
+				 * code in ProcessInterrupts(). FIXME: what similar code
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
 				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
@@ -3261,7 +3261,6 @@ void
 ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	Assert(InterruptHoldoffCount == 0);
 	Assert(CritSectionCount == 0);
 
 	{
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index becfc62fd32..2646ebc6c0f 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -526,9 +526,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;
-
-		CritSectionCount = 0;	/* should be unnecessary, but... */
+		ResetInterruptHoldoffCounts(0, 0);
 
 		/*
 		 * Note that we leave CurrentMemoryContext set to ErrorContext. The
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
index 239d58f7597..10234d4f987 100644
--- a/src/include/ipc/interrupt.h
+++ b/src/include/ipc/interrupt.h
@@ -15,6 +15,7 @@
 #ifndef IPC_INTERRUPT_H
 #define IPC_INTERRUPT_H
 
+#include "ipc/standard_interrupts.h"
 #include "port/atomics.h"
 #include "storage/procnumber.h"
 
@@ -23,212 +24,128 @@
  * needed which are needed by all callers of WaitInterrupt, so include it
  * here.
  *
- * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ * Note: InterruptMask is defined in waiteventset.h to avoid circular dependency
  */
 #include "storage/waiteventset.h"
 
-
 /*
- * Flags in the pending interrupts bitmask. Each value is a different bit, so that
- * these can be conveniently OR'd together.
- */
-#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
-
-/***********************************************************************
- * Begin definitions of built-in interrupt bits
- ***********************************************************************/
-
-/*
- * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
- * a process, which don't need a dedicated interrupt bit.
- */
-#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
-
-
-/***********************************************************************
- * Standard interrupts handled the same by most processes
+ * PendingInterrupts is used to receive and wait for interrupts.  The
+ * 'interrupts' field is a bitmask representing interrupts that are currently
+ * pending for the process.
  *
- * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
- * process startup has reached SetStandardInterrupts().
- ***********************************************************************/
-
-/*
- * Backend has been requested to terminate gracefully.
+ * 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.)
  *
- * 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.
+ * Attention mechanism
+ * -------------------
  *
- * 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.
+ * 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().
  *
- * 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.
+ * 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 is set, it also
+ * wakes it up.
  *
- * 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.
+ * 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.
  *
- * The interrupt is processed whenever starting to read from the client, or
- * when interrupted while doing so.
+ * 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.  The system tolerates spuriously set ATTENTION flag and wakeups,
+ * so that's OK.  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.
  */
-#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.
- ***********************************************************************/
+typedef struct
+{
+	pg_atomic_uint32 flags;		/* PI_FLAG_* */
 
-/* ask walsenders to prepare for shutdown  */
-#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_uint64 interrupts;	/* pending interrupts */
+	pg_atomic_uint64 attention_mask;	/* interrupts that set the ATTENTION
+										 * flag */
+#else
+	pg_atomic_uint32 interrupts_lo;
+	pg_atomic_uint32 interrupts_hi;
+	pg_atomic_uint32 attention_mask_lo;
+	pg_atomic_uint32 attention_mask_hi;
+#endif
+} PendingInterrupts;
 
-/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
-#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+#define PI_FLAG_ATTENTION		0x01
+#define PI_FLAG_SLEEPING		0x02
 
 /*
- * 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.
+ * 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.
  */
-#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
-
-/* Wake up startup process to check for the promotion signal file */
-#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
-
-/* sent to logical replication launcher, when a subscription changes */
-#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
-
-/* Graceful shutdown request for a parallel apply worker */
-#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
-
-/* Request checkpointer to perform one last checkpoint, then shut down. */
-#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
-
-#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+extern PGDLLIMPORT PendingInterrupts *MyPendingInterrupts;
 
 /*
- * 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
+ * Test if an interrupt is pending
  *
- * The remaining bits are handed out by RequestAddinInterrupt, for
- * extensions
- ***********************************************************************/
-#define BEGIN_ADDIN_INTERRUPTS 25
-#define END_ADDIN_INTERRUPTS 63
-
-/*
- * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
- * waiting for an interrupt.  If set, the backend needs to be woken up when a
- * bit in the pending interrupts mask is set.  It's used internally by the
- * interrupt machinery, and cannot be used directly in the public functions.
- * It's named differently to distinguish it from the actual interrupt flags.
- */
-#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
-
-extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
-
-/*
- * Test an interrupt flag (or flags).
+ * 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. This is used in
-	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
-	 *
-	 * That means that if the interrupt is concurrently set by another
-	 * process, we might miss it. That should be OK, because the next
-	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
-	 * We will see the updated value before sleeping.
+	 * 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.
 	 */
-	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+	uint64		pending;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_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)
 {
-	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+	uint64		mask = ~interruptMask;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	(void) pg_atomic_fetch_and_u64(&MyPendingInterrupts->interrupts, mask);
+#else
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_lo, (uint32) mask);
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_hi, (uint32) (mask >> 32));
+#endif
 }
 
 /*
@@ -254,106 +171,110 @@ extern void SwitchToLocalInterrupts(void);
 extern void SwitchToSharedInterrupts(void);
 extern void InitializeInterruptWaitSet(void);
 
-typedef void (*pg_interrupt_handler_t) (void);
-extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
-
-extern void EnableInterrupt(InterruptMask interruptMask);
-extern void DisableInterrupt(InterruptMask interruptMask);
-
-/* for extensions */
-extern InterruptMask RequestAddinInterrupt(void);
-
-/* Standard interrupt handlers. Defined in tcop/postgres.c */
-extern void SetStandardInterruptHandlers(void);
-
-extern void ProcessQueryCancelInterrupt(void);
-extern void ProcessTerminateInterrupt(void);
-extern void ProcessConfigReloadInterrupt(void);
-extern void ProcessAsyncNotifyInterrupt(void);
-extern void ProcessIdleStatsTimeoutInterrupt(void);
-extern void ProcessRecoveryConflictInterrupts(void);
-extern void ProcessTransactionTimeoutInterrupt(void);
-extern void ProcessIdleSessionTimeoutInterrupt(void);
-extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
-extern void ProcessClientCheckTimeoutInterrupt(void);
-extern void ProcessClientConnectionLost(void);
-
-extern void ProcessAuxProcessShutdownInterrupt(void);
-
 
 /*****************************************************************************
  *	  CHECK_FOR_INTERRUPTS() and friends
  *****************************************************************************/
 
+/* Interrupts currently enabled for CHECK_FOR_INTERRUPTS() processing */
+extern PGDLLIMPORT InterruptMask EnabledInterruptsMask;
 
-extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
-extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
-
-extern void ProcessInterrupts(void);
+/*
+ * 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;
 
-/* Test whether an interrupt is pending */
+/*
+ * 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
+ * if the HOLD_INTERRUPTS() is delaying the interrupt processing.
+ */
 #ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION(mask) \
-	(unlikely(InterruptPending(mask)))
+#define INTERRUPTS_PENDING_CONDITION() \
+	(unlikely(InterruptPending(EnabledInterruptsMask)))
 #else
-#define INTERRUPTS_PENDING_CONDITION(mask) \
+#define INTERRUPTS_PENDING_CONDITION() \
 	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
 	 pgwin32_dispatch_queued_signals() : (void) 0,	\
-	 unlikely(InterruptPending(mask)))
+	 unlikely(InterruptPending(EnabledInterruptsMask)))
 #endif
 
 /*
- * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
- *
- * (The interrupt handler may re-raise the interrupt, though)
+ * Can interrupts be processed in the current state, i.e. are the interrupts
+ * not prevented by the HOLD_INTERRUPTS() or a critical section critical
+ * section?
  */
-#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
-	(((mask) & CheckForInterruptsMask) == (mask))
+#define INTERRUPTS_CAN_BE_PROCESSED() \
+	(InterruptHoldoffCount == 0 && CritSectionCount == 0)
 
-/* Service interrupt, if one is pending and it's safe to service it now */
+/*
+ * 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.
+ *
+ * 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()					\
 do { \
-	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+	if (unlikely(pg_atomic_read_u32(MyPendingInterruptsFlags) != 0)) \
 		ProcessInterrupts();											\
 } while(0)
 
+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: */
-/*
- * XXX: is that still true? Should we use local vars to avoid repeated access
- * e.g. inside RESUME_INTERRUPTS() ?
- */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
+extern PGDLLIMPORT uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT uint32 CritSectionCount;
+
+extern const pg_atomic_uint32 ZeroPendingInterruptsFlags;
 
 static inline void
 HOLD_INTERRUPTS(void)
 {
 	InterruptHoldoffCount++;
-	CheckForInterruptsMask = (InterruptMask) 0;
+
+	/*
+	 * Disable CHECK_FOR_INTERRUPTS() by pointing MyPendingInterruptsFlags to
+	 * an all-zeros constant.
+	 */
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
 RESUME_INTERRUPTS(void)
 {
-	Assert(CheckForInterruptsMask == 0);
 	Assert(InterruptHoldoffCount > 0);
 	InterruptHoldoffCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
-	else
-		Assert(CheckForInterruptsMask == 0);
+		MyPendingInterruptsFlags = &MyPendingInterrupts->flags;
 }
 
 static inline void
 START_CRIT_SECTION(void)
 {
 	CritSectionCount++;
-	CheckForInterruptsMask = 0;
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
@@ -362,9 +283,9 @@ END_CRIT_SECTION(void)
 	Assert(CritSectionCount > 0);
 	CritSectionCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
-	else
-		Assert(CheckForInterruptsMask == 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/standard_interrupts.h b/src/include/ipc/standard_interrupts.h
new file mode 100644
index 00000000000..8907b378f4f
--- /dev/null
+++ b/src/include/ipc/standard_interrupts.h
@@ -0,0 +1,194 @@
+#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->interrupts bitmask.  Each
+ * value is a different bit, so that these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 64
+
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+#endif							/* IPC_STANDARD_INTERRUPTS_H */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 00ea9f86422..fab892f8b3c 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -325,7 +325,7 @@ struct PGPROC
 	dlist_node	lockGroupLink;	/* my member link, if I'm a member */
 
 	/* Bit mask of pending interrupts, waiting to be processed */
-	pg_atomic_uint64 pendingInterrupts;
+	PendingInterrupts pendingInterrupts;
 
 	/*
 	 * While in hot standby mode, shows that a conflict signal has been sent
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 9a5d8ac10e9..bfb8a001d81 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2184,6 +2184,7 @@ PatternInfoArray
 Pattern_Prefix_Status
 Pattern_Type
 PendingFsyncEntry
+PendingInterrupts
 PendingListenAction
 PendingListenEntry
 PendingRelDelete
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-02-18 00:11               ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-20 14:22                 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2026-03-06 14:30                   ` Heikki Linnakangas <[email protected]>
  1 sibling, 0 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2026-03-06 14:30 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 20/02/2026 16:22, Heikki Linnakangas wrote:
> On 18/02/2026 02:11, Heikki Linnakangas wrote:
>> On 14/02/2026 23:56, Andres Freund wrote:
>>> Could we have the mask of interrupts that WaitInterrupt() is waiting 
>>> for in a
>>> second variable? That way we could avoid interrupting WaitInterrupt() 
>>> when
>>> raising or sending a signal that WaitInterrupt() is not waiting for. 
>>> I think
>>> that can be one race-freely with a bit of care?
>>
>> Yeah, I thought of that, but I'm not sure what the right tradeoff here 
>> is. I doubt the spurious wakeups matter much in practice. Then again, 
>> maybe it's not much more complicated, so maybe I should try that.
>>
>> Now with this new version, the same consideration applies to the 
>> CFI_ATTENTION flag I added. We could expose a process's 
>> CheckForInterruptsMask alongside the pending interrupts, so it would 
>> be SendInterrupt()'s responsibility to check if the receiving 
>> backend's CheckForInterruptsMask includes the interrupt that's being 
>> sent. That would similarly eliminate the "false positive" 
>> ProcessInterrupts() calls from CHECK_FOR_INTERRUPTS(), by moving the 
>> logic to the senders. The SLEEPING_ON_INTERRUPTS and CFI_ATTENTION 
>> flags are quite symmetrical.
> 
> I tried that approach, exposing an "attention bitmask" where a backend 
> advertises which interrupts it's currently interested in. I think I like 
> it.
> 
> Patch attached. The relevant changes for this "attention mechanism" are 
> in the last patch, but there are some other small changes too so this 
> split into patches is a little messy. I moved the list of standard 
> interrupts to a separate header file, for example. So for reviewing, I 
> recommend reading the resulting interrupt.h and interrupt.c files after 
> applying all the patches, instead of trying to read the diff for those.

Here's another rebase, no other changes.

- Heikki


Attachments:

  [text/x-patch] v12-0001-Refactor-how-some-aux-processes-advertise-their-.patch (9.6K, ../../[email protected]/2-v12-0001-Refactor-how-some-aux-processes-advertise-their-.patch)
  download | inline diff:
From 66e05e49737446340615a7968c7d4074951227d5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 14:23:35 +0200
Subject: [PATCH v12 1/4] Refactor how some aux processes advertise their
 ProcNumber

This moves the responsibility of setting the
ProcGlobal->walrewriterProc and checkpointerProc fields to
InitAuxiliaryProcess. Also switch to the same pattern to advertise the
autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
field in shared memory. This can easily be extended to other aux
processes in the future, if other processes need to find them.

Switch to pg_atomic_uint32 for the fields. Seems easier to reason
about than volatile pointers. There was some precedence for that, as
were already using pg_atomic_uint32 for the procArrayGroupFirst and
clogGroupFirst fields, which also store ProcNumbers.

TODO: could also replace WalRecv->procno with this
---
 src/backend/access/transam/xlog.c     |  3 +--
 src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
 src/backend/postmaster/checkpointer.c | 14 ++++---------
 src/backend/postmaster/walwriter.c    |  6 ------
 src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
 src/include/storage/proc.h            |  7 ++++---
 6 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 354ac645bdc..129bb224d7d 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2636,8 +2636,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 
 	if (wakeup)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	walwriterProc = procglobal->walwriterProc;
+		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 6fde740465f..5d4eea4a1e3 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -277,7 +277,6 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -293,7 +292,6 @@ typedef struct AutoVacuumWorkItem
 typedef struct
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -564,8 +562,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		proc_exit(0);			/* done */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
-
 	/*
 	 * Create the initial database list.  The invariant we want this list to
 	 * keep is that it's ordered by decreasing next_worker.  As soon as an
@@ -799,8 +795,6 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
-
 	proc_exit(0);				/* done */
 }
 
@@ -1531,6 +1525,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (AutoVacuumShmem->av_startingWorker != NULL)
 	{
+		ProcNumber	launcherProc;
+
 		MyWorkerInfo = AutoVacuumShmem->av_startingWorker;
 		dbid = MyWorkerInfo->wi_dboid;
 		MyWorkerInfo->wi_proc = MyProc;
@@ -1549,8 +1545,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
+		if (launcherProc != INVALID_PROC_NUMBER)
+		{
+			int			pid = GetPGProcByNumber(launcherProc)->pid;
+
+			if (pid != 0)
+				kill(pid, SIGUSR2);
+		}
 	}
 	else
 	{
@@ -3393,7 +3395,6 @@ AutoVacuumShmemInit(void)
 
 		Assert(!found);
 
-		AutoVacuumShmem->av_launcherpid = 0;
 		dclist_init(&AutoVacuumShmem->av_freeWorkers);
 		dlist_init(&AutoVacuumShmem->av_runningWorkers);
 		AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index e03c19123bc..f1439d9a363 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,6 +47,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
@@ -348,12 +349,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	UpdateSharedMemoryConfig();
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->checkpointerProc = MyProcNumber;
-
 	/*
 	 * Loop until we've been asked to write the shutdown checkpoint or
 	 * terminate.
@@ -1125,7 +1120,7 @@ RequestCheckpoint(int flags)
 	for (ntries = 0;; ntries++)
 	{
 		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 		if (checkpointerProc == INVALID_PROC_NUMBER)
 		{
@@ -1266,8 +1261,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	/* ... but not till after we release the lock */
 	if (too_full)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
@@ -1548,7 +1542,7 @@ void
 WakeupCheckpointer(void)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
-	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
 		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 7c0e2809c17..a0ed1e61420 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -205,12 +205,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	hibernating = false;
 	SetWalWriterSleeping(false);
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->walwriterProc = MyProcNumber;
-
 	/*
 	 * Loop forever
 	 */
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index ccf9de0e67c..e001cb11803 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -211,8 +211,9 @@ InitProcGlobal(void)
 	dlist_init(&ProcGlobal->bgworkerFreeProcs);
 	dlist_init(&ProcGlobal->walsenderFreeProcs);
 	ProcGlobal->startupBufferPinWaitBufId = -1;
-	ProcGlobal->walwriterProc = INVALID_PROC_NUMBER;
-	ProcGlobal->checkpointerProc = INVALID_PROC_NUMBER;
+	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->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -711,6 +712,14 @@ InitAuxiliaryProcess(void)
 	 */
 	PGSemaphoreReset(MyProc->sem);
 
+	/* Some aux processes are also advertised in ProcGlobal */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
+	if (MyBackendType == B_WAL_WRITER)
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
+	if (MyBackendType == B_CHECKPOINTER)
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+
 	/*
 	 * Arrange to clean up at process exit.
 	 */
@@ -1055,6 +1064,23 @@ AuxiliaryProcKill(int code, Datum arg)
 	SwitchBackToLocalLatch();
 	pgstat_reset_wait_event_storage();
 
+	/* If this was one of aux processes advertised in ProcGlobal, clear it */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->avLauncherProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_WAL_WRITER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walwriterProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_CHECKPOINTER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	}
+
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 3f89450c216..4a1fafb9038 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -488,11 +488,12 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 clogGroupFirst;
 
 	/*
-	 * Current slot numbers of some auxiliary processes. There can be only one
+	 * Current proc numbers of some auxiliary processes. There can be only one
 	 * of each of these running at a time.
 	 */
-	ProcNumber	walwriterProc;
-	ProcNumber	checkpointerProc;
+	pg_atomic_uint32 avLauncherProc;
+	pg_atomic_uint32 walwriterProc;
+	pg_atomic_uint32 checkpointerProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
-- 
2.47.3



  [text/x-patch] v12-0002-Centralize-resetting-SIGCHLD-handler.patch (9.7K, ../../[email protected]/3-v12-0002-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From 2616894d546d94e790dc294c950f425105f7382a Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 8 Jan 2026 20:22:43 +0200
Subject: [PATCH v12 2/4] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 5d4eea4a1e3..0a730fb5b45 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -409,7 +409,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1416,7 +1415,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 8678ea4e139..133af5aee97 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -781,7 +781,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, SIG_IGN);
 	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 0956bd39a85..dbd670c16cc 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -108,11 +108,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index f1439d9a363..978a7d0e649 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -221,11 +221,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 82731e452fc..10a5ef9e15c 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -237,9 +237,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index cdbe53dd262..330c10ddb0b 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -234,11 +234,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 86c5e376b40..8515bfb9653 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -283,11 +283,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 742137edad6..4e4eed4d076 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -263,11 +263,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index a0ed1e61420..1851417ad1b 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -108,11 +108,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN); /* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 062a08ccb88..5e1ee7e4728 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1536,7 +1536,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGCHLD, SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 7c1b8757d7d..ec6c516530d 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -257,9 +257,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 2cde8ebc729..5acc8ee3ac8 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3743,9 +3743,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, SIG_DFL);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index d01a09dd0c4..5d66c80567c 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4285,13 +4285,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, SIG_DFL); /* system() requires this on some
-									 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 03f6c8479f2..fadf856d9bc 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -142,6 +142,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -154,6 +163,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] v12-0003-Replace-Latches-with-Interrupts.patch (499.1K, ../../[email protected]/4-v12-0003-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From 4b43fd099a744ff5e1c4a0e36530c17cbfe4929d Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Thu, 12 Feb 2026 17:07:28 +0200
Subject: [PATCH v12 3/4] Replace Latches with Interrupts

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

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

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

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

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

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

Most callers of WaitInterrupt (which replaces WaitLatch) use
CheckForInterruptMask, which is a bitmask that includes all the
interrupts that are handled by CHECK_FOR_INTERRUPTS() in the current
state. CHECK_FOR_INTERRUPTS() clears the interrupts bits that it
processes, the caller is no longer required to do ClearInterrupt (or
ResetLatch) for those bits. If you wake up for other interrupts that
are not handled by CHECK_FOR_INTERRUPTS(), you still need to clear
those.

Now that there are separate bits for different things, it's possible
to do e.g. WaitInterrupt(INTERRUPT_CONFIG_RELOAD |
INTERRUPT_QUERY_CANCEL) to wait just for a config reload request or
query cancellation, and not wake up on other events like a shutdown
request. That's not very useful in practice, as CHECK_FOR_INTERRUPTS()
still processes those interrupts if you call it, and you would want to
process all the standard interrupts promptly anyway. It might be
useful to check for specific interrupts with InterruptPending(),
without immediately processing them, however. spgdoinsert() does
something like that. In this commit I didn't change its overall logic,
but it probably could be made more fine-grained now.

More importantly, you can now have additional interrupts that are
processed less frequently and do not wake up the backend when it's not
prepared to process them. For example, the SINVAL_CATCHUP interrupt is
only processed when the backend is idle. Previously, a catchup request
would wake up the process whether it was ready to handle it or not;
now it only wakes up when the backend is idle. That's not too
significant for catchup interrupts because they don't occur that often
anyway, but it's still nice and could be more significant with other
interrupts.

There's still a general-purpose INTERRUPT_WAIT_WAKEUP interrupt that
is multiplexed for many different purposes in different processes, for
example to wake up the walwriter when it has work to do, and to wake
up processes waiting on a condition variable. The common property of
those uses is that there's some other flag or condition that is
checked on each wakeup, the wakeup interrupt itself means merely that
something interesting might've happened.

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

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

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

Similarly there's now a dedicated interrupt bit to notify the logical
replication launcher about subscription changes. That fixes the lost wakeup issue discussed at:
  Discussion: https://www.postgresql.org/message-id/[email protected]
  Discussion: https://www.postgresql.org/message-id/[email protected]

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

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

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

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

XXX: original text from Thomas's patch
--------------------------------------

"ProcSignals" were a two-step way for one backend to ask another backend
to do something, by multiplexing SIGUSR1.  Historically, real work was
done in the SIGUSR1 handler, but commit 0da096d7 removed the last case
of that.  Now the handler just set "interrupt pending" flags, called
SetLatch(MyLatch) to close races, and then did all the interesting work
at the next CHECK_FOR_INTERRUPTS() call.

This commit removes the ProcSignal step, replacing ProcSignals with
Interrupts.

Benefits of this refactoring include removing unnecessary concepts and
terminology, avoiding the risk of people putting buggy code back into
signal handlers, removing a whole lot of global variables, removing
dependencies on PIDs and the process model, removing risks of sending
a signal that has default action of termination to the wrong process,
and taking a step towards being able remove the fake signal system
from the Windows port.

Notable changes:

 * all calls to SendProcSignal(pid, PROCSIG_XXX) become
   SendInterrupt(INTERRUPT_XXX, procno)
 * all XxxPending global variables are gone; they are represented
   as bits in MyProc->pending_interrupts
 * the SIGUSR1 handler is gone
 * places that know the PIDs of other backends must now use procnos
   instead

The only code left in src/backend/storage/ipc/procsignal.c relates to
ProcSignalBarriers.  XXX Those should perhaps be renamed.

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

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index 89e187425cc..6be5ca9c183 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,17 +30,15 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
-#include "storage/procsignal.h"
 #include "storage/read_stream.h"
 #include "storage/smgr.h"
 #include "tcop/tcopprot.h"
@@ -170,10 +168,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. */
@@ -222,27 +223,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
 		{
@@ -266,14 +267,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);
 	}
 
 	/*
@@ -423,7 +423,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;
 
 		/*
@@ -444,7 +444,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)));
@@ -504,8 +504,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. */
@@ -923,9 +922,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),
@@ -958,9 +954,6 @@ apw_start_database_worker(void)
 	strcpy(worker.bgw_name, "autoprewarm worker");
 	strcpy(worker.bgw_type, "autoprewarm worker");
 
-	/* must set notify PID to wait for shutdown */
-	worker.bgw_notify_pid = MyProcPid;
-
 	if (!RegisterDynamicBackgroundWorker(&worker, &handle))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 311936406f2..0accaa760d5 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -22,13 +22,13 @@
 #include "commands/defrem.h"
 #include "common/base64.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/hsearch.h"
 #include "utils/inval.h"
@@ -831,8 +831,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);
@@ -1485,7 +1485,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))
@@ -1580,7 +1580,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))
@@ -1688,12 +1688,11 @@ pgfdw_get_cleanup_result(PGconn *conn, TimestampTz endtime,
 				pgfdw_we_cleanup_result = WaitEventExtensionNew("PostgresFdwCleanupResult");
 
 			/* Sleep until there's something to do */
-			wc = WaitLatchOrSocket(MyLatch,
-								   WL_LATCH_SET | WL_SOCKET_READABLE |
-								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-								   PQsocket(conn),
-								   cur_timeout, pgfdw_we_cleanup_result);
-			ResetLatch(MyLatch);
+			wc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_INTERRUPT | WL_SOCKET_READABLE |
+									   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+									   PQsocket(conn),
+									   cur_timeout, pgfdw_we_cleanup_result);
 
 			CHECK_FOR_INTERRUPTS();
 
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 60d90329a65..eb925fda39e 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -39,7 +39,7 @@
 #include "optimizer/tlist.h"
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
-#include "storage/latch.h"
+#include "storage/waiteventset.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
 #include "utils/guc.h"
@@ -7276,7 +7276,7 @@ postgresForeignAsyncConfigureWait(AsyncRequest *areq)
 		Assert(pendingAreq == areq);
 
 	AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
-					  NULL, areq);
+					  0, areq);
 }
 
 /*
diff --git a/doc/src/sgml/bgworker.sgml b/doc/src/sgml/bgworker.sgml
index 2affba74382..06bc57f10bd 100644
--- a/doc/src/sgml/bgworker.sgml
+++ b/doc/src/sgml/bgworker.sgml
@@ -63,7 +63,7 @@ typedef struct BackgroundWorker
     char        bgw_function_name[BGW_MAXLEN];
     Datum       bgw_main_arg;
     char        bgw_extra[BGW_EXTRALEN];
-    pid_t       bgw_notify_pid;
+    pid_t       bgw_notify_pid;			/* not used */
 } BackgroundWorker;
 </programlisting>
   </para>
@@ -131,6 +131,21 @@ typedef struct BackgroundWorker
      </listitem>
     </varlistentry>
 
+    <varlistentry>
+     <term><literal>BGWORKER_NO_NOTIFY</literal></term>
+     <listitem>
+      <para>
+       <indexterm><primary>BGWORKER_NO_NOTIFY</primary></indexterm> Normally,
+       the backend that registers a dynamic worker will be notified
+       with <literal>INTERRUPT_WAIT_WAKEUP</literal> when the worker's state
+       changes, which allows the caller to wait for the worker to start and
+       shut down.  That can be suppressed by setting this flag.
+       <!-- FIXME: Should we reserve a dedicated INTERRUPT bit for it? And if
+            we do, do we even need the BGWORKER_NO_NOTIFY flag, there's very little
+            overhead in sending a useless interrupt -->
+      </para>
+     </listitem>
+    </varlistentry>
    </variablelist>
 
   </para>
@@ -204,12 +219,8 @@ typedef struct BackgroundWorker
   </para>
 
   <para>
-   <structfield>bgw_notify_pid</structfield> is the PID of a PostgreSQL
-   backend process to which the postmaster should send <literal>SIGUSR1</literal>
-   when the process is started or exits.  It should be 0 for workers registered
-   at postmaster startup time, or when the backend registering the worker does
-   not wish to wait for the worker to start up.  Otherwise, it should be
-   initialized to <literal>MyProcPid</literal>.
+   <structfield>bgw_notify_pid</structfield> is not used and may be removed
+   in a future release.
   </para>
 
   <para>Once running, the process can connect to a database by calling
@@ -260,7 +271,7 @@ typedef struct BackgroundWorker
    reinitializes the cluster due to a backend failure.  Backends which need
    to suspend execution only temporarily should use an interruptible sleep
    rather than exiting; this can be achieved by calling
-   <function>WaitLatch()</function>. Make sure the
+   <function>WaitInterrupt()</function>. Make sure the
    <literal>WL_POSTMASTER_DEATH</literal> flag is set when calling that function, and
    verify the return code for a prompt exit in the emergency case that
    <command>postgres</command> itself has terminated.
@@ -291,10 +302,7 @@ typedef struct BackgroundWorker
 
   <para>
    In some cases, a process which registers a background worker may wish to
-   wait for the worker to start up.  This can be accomplished by initializing
-   <structfield>bgw_notify_pid</structfield> to <literal>MyProcPid</literal> and
-   then passing the <type>BackgroundWorkerHandle *</type> obtained at
-   registration time to
+   wait for the worker to start up.  This can be accomplished with the
    <function>WaitForBackgroundWorkerStartup(<parameter>BackgroundWorkerHandle
    *handle</parameter>, <parameter>pid_t *</parameter>)</function> function.
    This function will block until the postmaster has attempted to start the
diff --git a/doc/src/sgml/oauth-validators.sgml b/doc/src/sgml/oauth-validators.sgml
index 704089dd7b3..3c4268f741f 100644
--- a/doc/src/sgml/oauth-validators.sgml
+++ b/doc/src/sgml/oauth-validators.sgml
@@ -211,7 +211,7 @@
        correctly handle authentication timeouts and shutdown signals from
        <application>pg_ctl</application>. For example, blocking calls on sockets
        should generally be replaced with code that handles both socket events
-       and interrupts without races (see <function>WaitLatchOrSocket()</function>,
+       and interrupts without races (see <function>WaitInterruptOrSocket()</function>,
        <function>WaitEventSetWait()</function>, et al), and long-running loops
        should periodically call <function>CHECK_FOR_INTERRUPTS()</function>.
        Failure to follow this guidance may result in unresponsive backend
diff --git a/doc/src/sgml/sources.sgml b/doc/src/sgml/sources.sgml
index 760f9b69d47..62ff9e3e397 100644
--- a/doc/src/sgml/sources.sgml
+++ b/doc/src/sgml/sources.sgml
@@ -988,22 +988,25 @@ MemoryContextSwitchTo(MemoryContext context)
      call async-signal safe functions (as defined in POSIX) and access
      variables of type <literal>volatile sig_atomic_t</literal>. A few
      functions in <command>postgres</command> are also deemed signal safe, importantly
-     <function>SetLatch()</function>.
+     <function>RaiseInterrupt()</function>.
     </para>
     <para>
      In most cases signal handlers should do nothing more than note
      that a signal has arrived, and wake up code running outside of
-     the handler using a latch. An example of such a handler is the
+     the handler using RaiseInterrupt(). An example of such a handler is the
      following:
 <programlisting>
 static void
 handle_sighup(SIGNAL_ARGS)
 {
-    got_SIGHUP = true;
-    SetLatch(MyLatch);
+    RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
 }
 </programlisting>
     </para>
+    <!-- TODO: This section is not wrong, but should be updated. Should
+         document and emphasize the interrupt mechanism more, and the
+         handle_sighup example is bad because the standard signal handler for
+         SIGHUP already does that -->
    </simplesect>
 
    <simplesect id="source-conventions-function-pointers">
diff --git a/src/backend/Makefile b/src/backend/Makefile
index ba53cd9d998..6e1bac8ca7c 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 4d0da07135e..e8e84e248ce 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index c5d7db28077..06c996f6fc0 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb65a..ddf414660e0 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index ff927279cc3..460cd9f9d95 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index c9f143f6c31..b2ac6e247e6 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index dfffce3e396..9fc03134804 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index b64ccf5e912..fb7a9ea2a1d 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 9e714980d26..e13aa49731e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/transam.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index e88ddb32a05..1292406ebbe 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 0cefbacc96e..77e9390b1f9 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index 8cfb6ce75d6..5ae9114cd65 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8e220a3ae16..96172c32237 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 65c9f393f41..a8ec4be4b4d 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index d441122c426..0696d3b1099 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,13 +143,13 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
 #include "utils/lsyscache.h"
@@ -3305,11 +3305,10 @@ lazy_truncate_heap(LVRelState *vacrel)
 				return;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
-							 WAIT_EVENT_VACUUM_TRUNCATE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 VACUUM_TRUNCATE_LOCK_WAIT_INTERVAL,
+								 WAIT_EVENT_VACUUM_TRUNCATE);
 		}
 
 		/*
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 3047bd46def..da1952e9412 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -91,7 +91,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 5e89b86a62c..70a4e4ee00a 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index 95be0b17939..4d998adc2ac 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index d17aaa5aa0f..e007b2ea517 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 4125c185e8b..938f9047c36 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index 32ae0bda892..6f1f9dc0d82 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..b8b5eaac57a 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,8 +2035,11 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
+		 *
+		 * FIXME: CheckForInterruptsMask covers more than just query cancel
+		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION())
+		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 		{
 			result = false;
 			break;
@@ -2162,7 +2165,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION())
+			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
 			{
 				result = false;
 				break;
@@ -2338,7 +2341,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index 6b7117b56b2..884acb68c37 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/README b/src/backend/access/transam/README
index 231106270fd..73a727d53d6 100644
--- a/src/backend/access/transam/README
+++ b/src/backend/access/transam/README
@@ -794,7 +794,7 @@ commit to minimize the window in which the filesystem change has been made
 but the transaction isn't guaranteed committed.
 
 The walwriter regularly wakes up (via wal_writer_delay) or is woken up
-(via its latch, which is set by backends committing asynchronously) and
+(via an interrupt, which is sent by backends committing asynchronously) and
 performs an XLogBackgroundFlush().  This checks the location of the last
 completely filled WAL page.  If that has moved forwards, then we write all
 the changed buffers up to that point, so that under full load we write
diff --git a/src/backend/access/transam/README.parallel b/src/backend/access/transam/README.parallel
index 9df3da91b0c..a3593dd4f54 100644
--- a/src/backend/access/transam/README.parallel
+++ b/src/backend/access/transam/README.parallel
@@ -37,7 +37,7 @@ with fewer parallel workers than it originally requested, so catering to
 this case imposes no additional burden.
 
 Whenever a new message (or partial message; very large messages may wrap) is
-sent to the error-reporting queue, PROCSIG_PARALLEL_MESSAGE is sent to the
+sent to the error-reporting queue, INTERRUPT_PARALLEL_MESSAGE is sent to the
 initiating backend.  This causes the next CHECK_FOR_INTERRUPTS() in the
 initiating backend to read and rethrow the message.  For the most part, this
 makes error reporting in parallel mode "just work".  Of course, to work
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index e69c4b74248..c668a26d5a0 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -28,6 +28,7 @@
 #include "commands/async.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
@@ -115,9 +116,6 @@ typedef struct FixedParallelState
  */
 int			ParallelWorkerNumber = -1;
 
-/* Is there a parallel message pending which we need to receive? */
-volatile sig_atomic_t ParallelMessagePending = false;
-
 /* Are we initializing a parallel worker? */
 bool		InitializingParallelWorker = false;
 
@@ -127,9 +125,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.
@@ -243,7 +238,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED())
+	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
 		pcxt->nworkers = 0;
 
 	/*
@@ -612,7 +607,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.
@@ -767,16 +761,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();
+				}
 			}
 		}
 
@@ -885,15 +884,16 @@ WaitForParallelWorkersToFinish(ParallelContext *pcxt)
 				 * the worker writes messages and terminates after the
 				 * CHECK_FOR_INTERRUPTS() near the top of this function and
 				 * before the call to GetBackgroundWorkerPid().  In that case,
-				 * our latch should have been set as well and the right things
-				 * will happen on the next pass through the loop.
+				 * INTERRUPT_WAIT_WAKEUP should have been set as well and the
+				 * right things will happen on the next pass through the loop.
 				 */
 			}
 		}
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-						 WAIT_EVENT_PARALLEL_FINISH);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, -1,
+							 WAIT_EVENT_PARALLEL_FINISH);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	if (pcxt->toc != NULL)
@@ -1034,21 +1034,6 @@ ParallelContextActive(void)
 	return !dlist_is_empty(&pcxt_list);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel worker message.
- *
- * Note: this is called within a signal handler!  All we can do is set
- * a flag that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelMessages().
- */
-void
-HandleParallelMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from parallel workers.
  */
@@ -1083,9 +1068,7 @@ ProcessParallelMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpm_context);
 
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	ParallelMessagePending = false;
-
+	/* OK to process messages */
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1327,7 +1310,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. */
@@ -1363,8 +1349,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));
 
@@ -1380,8 +1365,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
@@ -1620,9 +1604,8 @@ ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end)
 static void
 ParallelWorkerShutdown(int code, Datum arg)
 {
-	SendProcSignal(ParallelLeaderPid,
-				   PROCSIG_PARALLEL_MESSAGE,
-				   ParallelLeaderProcNumber);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  ParallelLeaderProcNumber);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 549c7e3e64b..5592df30f8d 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 5f65fa7b80f..fbffa90b1f0 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,6 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 129bb224d7d..29d808d756a 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -70,6 +70,7 @@
 #include "common/controldata_utils.h"
 #include "common/file_utils.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
 #include "pgstat.h"
@@ -88,7 +89,6 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/large_object.h"
-#include "storage/latch.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2639,7 +2639,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);
 	}
 }
 
@@ -9496,11 +9496,11 @@ do_pg_backup_stop(BackupState *state, bool waitforarchive)
 				reported_waiting = true;
 			}
 
-			(void) WaitLatch(MyLatch,
-							 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							 1000L,
-							 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								 1000L,
+								 WAIT_EVENT_BACKUP_WAIT_WAL_ARCHIVE);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			if (++waits >= seconds_before_warning)
 			{
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2efe4105efb..8c74eceec18 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -24,11 +24,11 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.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"
@@ -718,17 +718,15 @@ pg_promote(PG_FUNCTION_ARGS)
 	{
 		int			rc;
 
-		ResetLatch(MyLatch);
-
 		if (!RecoveryInProgress())
 			PG_RETURN_BOOL(true);
 
 		CHECK_FOR_INTERRUPTS();
 
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-					   1000L / WAITS_PER_SECOND,
-					   WAIT_EVENT_PROMOTE);
+		rc = WaitInterrupt(CheckForInterruptsMask,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+						   1000L / WAITS_PER_SECOND,
+						   WAIT_EVENT_PROMOTE);
 
 		/*
 		 * Emergency bailout if postmaster has died.  This is to avoid the
diff --git a/src/backend/access/transam/xlogrecovery.c b/src/backend/access/transam/xlogrecovery.c
index fbddd7e522c..3cd2588b3f1 100644
--- a/src/backend/access/transam/xlogrecovery.c
+++ b/src/backend/access/transam/xlogrecovery.c
@@ -44,6 +44,7 @@
 #include "catalog/pg_control.h"
 #include "commands/tablespace.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "pgstat.h"
@@ -54,8 +55,8 @@
 #include "replication/walreceiver.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/spin.h"
 #include "utils/datetime.h"
@@ -409,7 +410,6 @@ XLogRecoveryShmemInit(void)
 	memset(XLogRecoveryCtl, 0, sizeof(XLogRecoveryCtlData));
 
 	SpinLockInit(&XLogRecoveryCtl->info_lck);
-	InitSharedLatch(&XLogRecoveryCtl->recoveryWakeupLatch);
 	ConditionVariableInit(&XLogRecoveryCtl->recoveryNotPausedCV);
 }
 
@@ -483,13 +483,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).
@@ -1592,13 +1585,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);
 }
 
 /*
@@ -1728,8 +1714,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
@@ -1758,8 +1744,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))
 			{
@@ -1780,9 +1766,8 @@ PerformWalRecovery(void)
 			ApplyWalRecord(xlogreader, record, &replayTLI);
 
 			/*
-			 * If we replayed an LSN that someone was waiting for then walk
-			 * over the shared memory array and set latches to notify the
-			 * waiters.
+			 * If we replayed an LSN that someone was waiting for, wake them
+			 * up.
 			 */
 			if (waitLSNState &&
 				(XLogRecoveryCtl->lastReplayedEndRecPtr >=
@@ -2916,7 +2901,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;
 
@@ -2993,8 +2978,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)
@@ -3002,11 +2987,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;
 
@@ -3027,10 +3017,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;
 }
@@ -3708,16 +3700,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;
@@ -3983,11 +3975,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;
 				}
 
@@ -4003,11 +3996,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 */
@@ -4439,11 +4429,11 @@ CheckForStandbyTrigger(void)
 	if (LocalPromoteIsTriggered)
 		return true;
 
-	if (IsPromoteSignaled() && CheckPromoteSignal())
+	if (InterruptPending(INTERRUPT_CHECK_PROMOTE) && CheckPromoteSignal())
 	{
 		ereport(LOG, (errmsg("received promote request")));
 		RemovePromoteSignalFiles();
-		ResetPromoteSignaled();
+		ClearInterrupt(INTERRUPT_CHECK_PROMOTE);
 		SetPromoteIsTriggered();
 		return true;
 	}
@@ -4481,7 +4471,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);
 }
 
 /*
@@ -4685,7 +4678,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 d286ff63123..efe08c4ebfe 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -24,9 +24,10 @@
  *
  *		In addition, the least-awaited LSN for each type is cached in the
  *		minWaitedLSN array.  The waiter process publishes information about
- *		itself to the shared memory and waits on the latch until it is woken
- *		up by the appropriate process, standby is promoted, or the postmaster
- *		dies.  Then, it cleans information about itself in the shared memory.
+ *		itself to the shared memory and waits on INTERRUPT_WAIT_WAKEUP until
+ *		it is woken up by the appropriate process, standby is promoted, or the
+ *		postmaster dies.  Then, it cleans information about itself in the
+ *		shared memory.
  *
  *		On standby servers:
  *		- After replaying a WAL record, the startup process performs a fast
@@ -54,7 +55,6 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/shmem.h"
 #include "utils/fmgrprotos.h"
@@ -68,7 +68,7 @@ static int	waitlsn_cmp(const pairingheap_node *a, const pairingheap_node *b,
 struct WaitLSNState *waitLSNState = NULL;
 
 /*
- * Wait event for each WaitLSNType, used with WaitLatch() to report
+ * Wait event for each WaitLSNType, used with WaitInterrupt() to report
  * the wait in pg_stat_activity.
  */
 static const uint32 WaitLSNWaitEvents[] = {
@@ -241,9 +241,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
@@ -298,14 +298,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);
 }
@@ -364,7 +363,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.
@@ -376,7 +375,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);
@@ -434,8 +433,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
@@ -447,8 +447,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout)
 					errmsg("terminating connection due to unexpected postmaster exit"),
 					errcontext("while waiting for LSN"));
 
-		if (rc & WL_LATCH_SET)
-			ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	/*
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index e112eed7485..e49fdc1efa2 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,9 +15,9 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 typedef struct bbsink_throttle
@@ -146,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 (;;)
@@ -163,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/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 0b6d119dad0..035a10aeee6 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -36,7 +36,7 @@
  *	  is no need for WAL support or fsync'ing.
  *
  * 3. Every backend that is listening on at least one channel registers by
- *	  entering its PID into the array in AsyncQueueControl. It then scans all
+ *	  entering itself into the array in AsyncQueueControl. It then scans all
  *	  incoming notifications in the central queue and first compares the
  *	  database OID of the notification with its own database OID and then
  *	  compares the notified channel with the list of channels that it listens
@@ -68,8 +68,8 @@
  *	  make any required updates to the effective listen state (see below).
  *	  Then we signal any backends that may be interested in our messages
  *	  (including our own backend, if listening).  This is done by
- *	  SignalBackends(), which sends a PROCSIG_NOTIFY_INTERRUPT signal to
- *	  each relevant backend, as described below.
+ *	  SignalBackends(), which sends INTERRUPT_ASYNC_NOTIFY to each relevant
+ *	  backend, as described below.
  *
  *	  Finally, after we are out of the transaction altogether and about to go
  *	  idle, we scan the queue for messages that need to be sent to our
@@ -82,13 +82,11 @@
  *	  frontend until the command is done; but notifies to other backends
  *	  should go out immediately after each commit.
  *
- * 5. Upon receipt of a PROCSIG_NOTIFY_INTERRUPT signal, the signal handler
- *	  sets the process's latch, which triggers the event to be processed
- *	  immediately if this backend is idle (i.e., it is waiting for a frontend
+ * 5. The INTERRUPT_ASYNC_NOTIFY interrupt is processed immediately in the
+ *	  listening backend if it is idle (i.e., it is waiting for a frontend
  *	  command and is not within a transaction block. C.f.
- *	  ProcessClientReadInterrupt()).  Otherwise the handler may only set a
- *	  flag, which will cause the processing to occur just before we next go
- *	  idle.
+ *	  main loop in PostgresBackend()).  Otherwise the interrupt is processed
+ *	  later, just before we next go idle.
  *
  *	  Inbound-notify processing consists of reading all of the notifications
  *	  that have arrived since scanning last time. We read every notification
@@ -126,9 +124,8 @@
  *
  *	  SignalBackends() consults the shared global channel table to identify
  *	  listeners for the channels that the current transaction sent
- *	  notification(s) to.  Each selected backend is marked as having a wakeup
- *	  pending to avoid duplicate signals, and a PROCSIG_NOTIFY_INTERRUPT
- *	  signal is sent to it.
+ *	  notification(s) to.  INTERRUPT_ASYNC_NOTIFY is sent to each selected
+ *	  backend.
  *
  * 8. While writing notifications, PreCommit_Notify() records the queue head
  *	  position both before and after the write.  Because all writers serialize
@@ -160,7 +157,6 @@
 
 #include <limits.h>
 #include <unistd.h>
-#include <signal.h>
 
 #include "access/parallel.h"
 #include "access/slru.h"
@@ -170,15 +166,14 @@
 #include "commands/async.h"
 #include "common/hashfn.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "storage/dsm_registry.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
-#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/dsa.h"
@@ -289,7 +284,8 @@ typedef struct QueueBackendStatus
 	Oid			dboid;			/* backend's database OID, or InvalidOid */
 	ProcNumber	nextListener;	/* id of next listener, or INVALID_PROC_NUMBER */
 	QueuePosition pos;			/* backend has read queue up to here */
-	bool		wakeupPending;	/* signal sent to backend, not yet processed */
+	bool		wakeupPending;	/* interrupt sent to backend, not yet
+								 * processed */
 	bool		isAdvancing;	/* backend is advancing its position */
 } QueueBackendStatus;
 
@@ -319,7 +315,7 @@ typedef struct QueueBackendStatus
  * globalChannelTable partition locks.
  *
  * Each backend uses the backend[] array entry with index equal to its
- * ProcNumber.  We rely on this to make SendProcSignal fast.
+ * ProcNumber.  We rely on this for sending interrupts.
  *
  * The backend[] array entries for actively-listening backends are threaded
  * together using firstListener and the nextListener links, so that we can
@@ -528,15 +524,6 @@ typedef struct ChannelName
 	char		channel[NAMEDATALEN];	/* hash key */
 } ChannelName;
 
-/*
- * Inbound notifications are initially processed by HandleNotifyInterrupt(),
- * called from inside a signal handler. That just sets the
- * notifyInterruptPending flag and sets the process
- * latch. ProcessNotifyInterrupt() will then be called whenever it's safe to
- * actually deal with the interrupt.
- */
-volatile sig_atomic_t notifyInterruptPending = false;
-
 /* True if we've registered an on_shmem_exit cleanup */
 static bool unlistenExitRegistered = false;
 
@@ -553,11 +540,10 @@ static QueuePosition queueHeadBeforeWrite;
 static QueuePosition queueHeadAfterWrite;
 
 /*
- * Workspace arrays for SignalBackends.  These are preallocated in
+ * Workspace array for SignalBackends.  This is preallocated in
  * PreCommit_Notify to avoid needing memory allocation after committing to
  * clog.
  */
-static int32 *signalPids = NULL;
 static ProcNumber *signalProcnos = NULL;
 
 /* have we advanced to a page that's a multiple of QUEUE_CLEANUP_DELAY? */
@@ -1266,10 +1252,6 @@ PreCommit_Notify(void)
 		}
 
 		/* Preallocate workspace that will be needed by SignalBackends() */
-		if (signalPids == NULL)
-			signalPids = MemoryContextAlloc(TopMemoryContext,
-											MaxBackends * sizeof(int32));
-
 		if (signalProcnos == NULL)
 			signalProcnos = MemoryContextAlloc(TopMemoryContext,
 											   MaxBackends * sizeof(ProcNumber));
@@ -1361,7 +1343,7 @@ PreCommit_Notify(void)
  *
  *		Apply pending listen/unlisten changes and clear transaction-local state.
  *
- *		If we issued any notifications in the transaction, send signals to
+ *		If we issued any notifications in the transaction, send interrupts to
  *		listening backends (possibly including ourselves) to process them.
  *		Also, if we filled enough queue pages with new notifies, try to
  *		advance the queue tail pointer.
@@ -1387,9 +1369,9 @@ AtCommit_Notify(void)
 		asyncQueueUnregister();
 
 	/*
-	 * Send signals to listening backends.  We need do this only if there are
-	 * pending notifies, which were previously added to the shared queue by
-	 * PreCommit_Notify().
+	 * Send interrupts to listening backends.  We need do this only if there
+	 * are pending notifies, which were previously added to the shared queue
+	 * by PreCommit_Notify().
 	 */
 	if (pendingNotifies != NULL)
 		SignalBackends();
@@ -1619,7 +1601,7 @@ PrepareTableEntriesForListen(const char *channel)
  *
  * Prepare an UNLISTEN by recording it in pendingListenActions, but only if
  * we're currently listening (committed or staged).  We don't touch
- * globalChannelTable yet - the listener keeps receiving signals until
+ * globalChannelTable yet - the listener keeps receiving notifications until
  * commit, when the entry is removed.
  */
 static void
@@ -1639,7 +1621,7 @@ PrepareTableEntriesForUnlisten(const char *channel)
 	/*
 	 * Record in local pending hash that we want to UNLISTEN, overwriting any
 	 * earlier attempt to LISTEN.  Don't touch localChannelTable or
-	 * globalChannelTable yet - we keep receiving signals until commit.
+	 * globalChannelTable yet - we keep receiving notifications until commit.
 	 */
 	pending = (PendingListenEntry *)
 		hash_search(pendingListenActions, channel, HASH_ENTER, NULL);
@@ -2243,14 +2225,14 @@ asyncQueueFillWarning(void)
 }
 
 /*
- * Send signals to listening backends.
+ * Send interrupts to listening backends.
  *
  * Normally we signal only backends that are interested in the notifies that
  * we just sent.  However, that will leave idle listeners falling further and
  * further behind.  Waken them anyway if they're far enough behind, so they'll
  * advance their queue position pointers, allowing the global tail to advance.
  *
- * Since we know the ProcNumber and the Pid the signaling is quite cheap.
+ * Since we know the ProcNumber the signaling is quite cheap.
  *
  * This is called during CommitTransaction(), so it's important for it
  * to have very low probability of failure.
@@ -2263,13 +2245,13 @@ SignalBackends(void)
 	/* Can't get here without PreCommit_Notify having made the global table */
 	Assert(globalChannelTable != NULL);
 
-	/* It should have set up these arrays, too */
-	Assert(signalPids != NULL && signalProcnos != NULL);
+	/* It should have set up this array, too */
+	Assert(signalProcnos != NULL);
 
 	/*
 	 * Identify backends that we need to signal.  We don't want to send
 	 * signals while holding the NotifyQueueLock, so this part just builds a
-	 * list of target PIDs in signalPids[] and signalProcnos[].
+	 * list of target backends in signalProcnos[].
 	 */
 	count = 0;
 
@@ -2294,7 +2276,6 @@ SignalBackends(void)
 		for (int j = 0; j < entry->numListeners; j++)
 		{
 			ProcNumber	i;
-			int32		pid;
 			QueuePosition pos;
 
 			if (!listeners[j].listening)
@@ -2305,16 +2286,14 @@ SignalBackends(void)
 			if (QUEUE_BACKEND_WAKEUP_PENDING(i))
 				continue;		/* already signaled, no need to repeat */
 
-			pid = QUEUE_BACKEND_PID(i);
 			pos = QUEUE_BACKEND_POS(i);
 
 			if (QUEUE_POS_EQUAL(pos, QUEUE_HEAD))
 				continue;		/* it's fully caught up already */
 
-			Assert(pid != InvalidPid);
+			Assert(i != INVALID_PROC_NUMBER);
 
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2331,7 +2310,6 @@ SignalBackends(void)
 	 */
 	for (ProcNumber i = QUEUE_FIRST_LISTENER; i != INVALID_PROC_NUMBER; i = QUEUE_NEXT_LISTENER(i))
 	{
-		int32		pid;
 		QueuePosition pos;
 
 		if (QUEUE_BACKEND_WAKEUP_PENDING(i))
@@ -2341,7 +2319,6 @@ SignalBackends(void)
 		if (QUEUE_BACKEND_IS_ADVANCING(i))
 			continue;
 
-		pid = QUEUE_BACKEND_PID(i);
 		pos = QUEUE_BACKEND_POS(i);
 
 		/*
@@ -2361,10 +2338,7 @@ SignalBackends(void)
 									QUEUE_POS_PAGE(pos)) >= QUEUE_CLEANUP_DELAY)
 		{
 			/* It's idle and far behind, so wake it up */
-			Assert(pid != InvalidPid);
-
 			QUEUE_BACKEND_WAKEUP_PENDING(i) = true;
-			signalPids[count] = pid;
 			signalProcnos[count] = i;
 			count++;
 		}
@@ -2372,29 +2346,20 @@ SignalBackends(void)
 
 	LWLockRelease(NotifyQueueLock);
 
-	/* Now send signals */
+	/* Now send the interrupts */
 	for (int i = 0; i < count; i++)
 	{
-		int32		pid = signalPids[i];
+		ProcNumber	procno = signalProcnos[i];
 
 		/*
-		 * If we are signaling our own process, no need to involve the kernel;
-		 * just set the flag directly.
+		 * Note: it's unlikely but certainly possible that the backend exited
+		 * since we released NotifyQueueLock. We'll send the interrupt to
+		 * wrong backend in that case, but that's harmless.
 		 */
-		if (pid == MyProcPid)
-		{
-			notifyInterruptPending = true;
-			continue;
-		}
-
-		/*
-		 * Note: assuming things aren't broken, a signal failure here could
-		 * only occur if the target backend exited since we released
-		 * NotifyQueueLock; which is unlikely but certainly possible. So we
-		 * just log a low-level debug message if it happens.
-		 */
-		if (SendProcSignal(pid, PROCSIG_NOTIFY_INTERRUPT, signalProcnos[i]) < 0)
-			elog(DEBUG3, "could not signal backend with PID %d: %m", pid);
+		if (procno == MyProcNumber)
+			RaiseInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		else
+			SendInterrupt(INTERRUPT_ASYNC_NOTIFY, procno);
 	}
 }
 
@@ -2531,39 +2496,12 @@ AtSubAbort_Notify(void)
 	}
 }
 
-/*
- * HandleNotifyInterrupt
- *
- *		Signal handler portion of interrupt handling. Let the backend know
- *		that there's a pending notify interrupt. If we're currently reading
- *		from the client, this will interrupt the read and
- *		ProcessClientReadInterrupt() will call ProcessNotifyInterrupt().
- */
-void
-HandleNotifyInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	/* signal that work needs to be done */
-	notifyInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessNotifyInterrupt
  *
- *		This is called if we see notifyInterruptPending set, just before
- *		transmitting ReadyForQuery at the end of a frontend command, and
- *		also if a notify signal occurs while reading from the frontend.
- *		HandleNotifyInterrupt() will cause the read to be interrupted
- *		via the process's latch, and this routine will get called.
- *		If we are truly idle (ie, *not* inside a transaction block),
- *		process the incoming notifies.
+ *		This is called if we see INTERRUPT_ASYNC_NOTIFY set, just before
+ *		transmitting ReadyForQuery at the end of a frontend command, and also
+ *		if the interrupt occurs while reading from the frontend.
  *
  *		If "flush" is true, force any frontend messages out immediately.
  *		This can be false when being called at the end of a frontend command,
@@ -2572,12 +2510,13 @@ HandleNotifyInterrupt(void)
 void
 ProcessNotifyInterrupt(bool flush)
 {
-	if (IsTransactionOrTransactionBlock())
-		return;					/* not really idle */
+	Assert(!IsTransactionOrTransactionBlock());
 
-	/* Loop in case another signal arrives while sending messages */
-	while (notifyInterruptPending)
+	/* Loop in case another interrupt arrives while sending messages */
+	do
+	{
 		ProcessIncomingNotify(flush);
+	} while (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY));
 }
 
 
@@ -2687,7 +2626,7 @@ asyncQueueReadAllNotifications(void)
 			 * Our stop position is what we found to be the head's position
 			 * when we entered this function. It might have changed already.
 			 * But if it has, we will receive (or have already received and
-			 * queued) another signal and come here again.
+			 * queued) another interrupt and come here again.
 			 *
 			 * We are not holding NotifyQueueLock here! The queue can only
 			 * extend beyond the head pointer (see above) and we leave our
@@ -3051,9 +2990,6 @@ AsyncNotifyFreezeXids(TransactionId newFrozenXid)
 static void
 ProcessIncomingNotify(bool flush)
 {
-	/* We *must* reset the flag */
-	notifyInterruptPending = false;
-
 	/* Do nothing else if we aren't actively listening */
 	if (LocalChannelTableIsEmpty())
 		return;
diff --git a/src/backend/commands/copyfromparse.c b/src/backend/commands/copyfromparse.c
index fbd13353efc..ea2659821a9 100644
--- a/src/backend/commands/copyfromparse.c
+++ b/src/backend/commands/copyfromparse.c
@@ -270,9 +270,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)
@@ -304,7 +308,10 @@ CopyGetData(CopyFromState cstate, void *databuf, int minread, int maxread)
 						ereport(ERROR,
 								(errcode(ERRCODE_CONNECTION_FAILURE),
 								 errmsg("unexpected EOF on client connection with an open transaction")));
-					RESUME_CANCEL_INTERRUPTS();
+
+					if (save_query_cancel_enabled)
+						EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 					/* ... and process it */
 					switch (mtype)
 					{
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index b9840637783..4e879ef7ad1 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,12 +42,12 @@
 #include "commands/defrem.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
@@ -2434,8 +2434,7 @@ vacuum_delay_point(bool is_analyze)
 	/* Always check for interrupts */
 	CHECK_FOR_INTERRUPTS();
 
-	if (InterruptPending ||
-		(!VacuumCostActive && !ConfigReloadPending))
+	if (!VacuumCostActive && !InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		return;
 
 	/*
@@ -2444,9 +2443,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();
 	}
@@ -2533,8 +2532,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 39d9442c121..1d5064eb29e 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -61,10 +61,9 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
 #include "pgstat.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
+#include "utils/resowner.h"
 
 /* Shared state for parallel-aware Append. */
 struct ParallelAppendState
@@ -1044,7 +1043,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;
@@ -1068,8 +1067,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,
@@ -1079,8 +1078,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)
@@ -1123,14 +1122,10 @@ ExecAppendAsyncEventWait(AppendState *node)
 				ExecAsyncNotify(areq);
 			}
 		}
-
-		/* Handle standard interrupts */
-		if ((w->events & WL_LATCH_SET) != 0)
-		{
-			ResetLatch(MyLatch);
-			CHECK_FOR_INTERRUPTS();
-		}
 	}
+
+	/* Handle standard interrupts */
+	CHECK_FOR_INTERRUPTS();
 }
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 114693abb32..a37d6e906b3 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,9 +34,8 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
-#include "storage/latch.h"
 #include "utils/wait_event.h"
 
 
@@ -383,9 +382,11 @@ gather_readnext(GatherState *gatherstate)
 				return NULL;
 
 			/* Nothing to do except wait for developments. */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 WAIT_EVENT_EXECUTE_GATHER);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask |
+								 INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 WAIT_EVENT_EXECUTE_GATHER);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			nvisited = 0;
 		}
 	}
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..7d465bc97db
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,19 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	interrupt.o \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/README.md b/src/backend/ipc/README.md
new file mode 100644
index 00000000000..459f3d3cae4
--- /dev/null
+++ b/src/backend/ipc/README.md
@@ -0,0 +1,260 @@
+Interrupts
+==========
+
+"Interrupts" are a set of flags that represent conditions that should
+be handled at a later time.  They are roughly analogous to Unix
+signals, except that they are handled cooperatively by checking for
+them at many points in the code. The CHECK_FOR_INTERRUPTS() macro is
+called at strategically located spots where it is normally safe to
+process interrupts.
+
+Well behaved backend code calls CHECK_FOR_INTERRUPTS() periodically in
+any long-running loops or other long computations, and never sleeps
+using mechanisms other than the WaitEventSet mechanism or the more
+convenient WaitInterrupt / WaitSockerOrInterrupt functions (except for
+bounded short periods, eg LWLock waits). Following these rules ensures
+that the backend doesn't become uncancellable for too long.
+
+Each interrupt type can have a handler function associated with it,
+which will be called by CHECK_FOR_INTERRUPTS() if the interrupt is
+pending.  A standard set of interrupt handlers are installed at
+process startup, but they can be overridden by
+SetInterruptHandler(). A handler can also be temporarily disabled by
+calling DisableInterrupt and re-enabled by EnableInterrupt.
+
+If an interrupt is raised that doesn't have a handler function or it
+is not enabled, the interrupt will remain pending until it is cleared,
+or the handler is re-enabled.
+
+If an interrupt is sent that was already pending for the target
+process, it is coalesced, ie. the target process will process it only
+once. Attempting to set an interrupt bit that is already set is very
+fast, amounting to merely an atomic op in shared memory.
+
+See src/include/ipc/interrupt.h for a list of all the interrupts used
+in core code. Extensions can define their own interrupts.
+
+HOLD/RESUME_INTERRUPTS
+----------------------
+
+Processing an interrupt may throw an error with ereport() (for
+example, query cancellation) or terminate the process gracefully. In
+some cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level
+subroutines that might sometimes be called in contexts that do *not*
+want to allow interrupt processing.  The HOLD_INTERRUPTS() and
+RESUME_INTERRUPTS() macros allow code to ensure that no interrupt
+handlers are called, even if CHECK_FOR_INTERRUPTS() gets called in a
+subroutine.  The interrupt will be held off until
+CHECK_FOR_INTERRUPTS() is done outside any HOLD_INTERRUPTS() ...
+RESUME_INTERRUPTS() section.
+
+HOLD_INTERRUPTS() blocks can be nested. If HOLD_INTERRUPTS() is called
+multiple times, interrupts are held until every HOLD_INTERRUPTS() call
+has been balanced with a RESUME_INTERRUPTS() call.
+
+Critical sections
+-----------------
+
+A related, but conceptually distinct, mechanism is the "critical
+section" mechanism.  A critical section not only holds off cancel/die
+interrupts, but causes any ereport(ERROR) or ereport(FATAL) to become
+ereport(PANIC) --- that is, a system-wide reset is forced.  Needless
+to say, only really *critical* code should be marked as a critical
+section! This mechanism is mostly used for XLOG-related code.
+
+Sending interrupts
+------------------
+
+Interrupt flags can be "raised" in different ways:
+- synchronously by code that wants to defer an action, by calling
+  RaiseInterrupt,
+- asynchronously by timers or other signal handlers, also by calling
+  RaiseInterrupt, or
+- "sent" by other backends with SendInterrupt
+
+Processing interrupts
+---------------------
+
+Interrupts are usually handled by handler functions, which are called
+from CHECK_FOR_INTERRUPTS() when the corresponding interrupt is
+pending. Use SetInterruptHandler() to install a handler function.
+
+Sometimes you don't want an interrupt to be processed at any random
+CHECK_FOR_INTERRUPTS() invocation. In that case, you can leave the
+interrupt handler disabled, and check for the interrupt explicitly at
+suitable spots with InterruptPending().
+
+INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
+interrupt needs to be serviced, without trying to do so immediately.
+Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
+which tells whether CHECK_FOR_INTERRUPTS() is sure to clear the
+interrupt.
+
+
+Waiting for an interrupt
+------------------------
+The correct pattern to wait for an event(s) that raises an interrupt
+is:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	/* Check if our interrupt was raised (clearing it if it was) */
+	if (ConsumeInterrupt(INTERRUPT_PRINTER_ON_FIRE))
+		break;
+
+	/*
+	 * Sleep until our interrupt or one of the standard interrupts is
+	 * received.
+	 */
+	WaitInterrupt(INTERRUPT_CFI_MASK |
+				  INTERRUPT_PRINTER_ON_FIRE,
+				  ...);
+}
+```
+
+Often an interrupt is used to wake up a process that is known to be
+waiting and not doing anything else. The INTERRUPT_WAIT_WAKEUP
+interrupt is reserved for such use cases where you don't need a
+dedicated interrupt flag. When using INTERRUPT_WAIT_WAKEUP, you need
+some other signaling, e.g. a flag in shared memory, to indicate that
+the desired event has happen, as you may sometimes receive false
+wakeups. The pattern using INTERRUPT_WAIT_WAKEUP looks like this:
+
+```
+for (;;)
+{
+	/* Handle any other interrupts received while we're waiting */
+	CHECK_FOR_INTERRUPTS();
+
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+}
+```
+
+It's important to clear the interrupt *before* checking if there's
+work to do. Otherwise, if someone sets the interrupt between the check
+and the ClearInterrupt() call, you will miss it and Wait will
+incorrectly block. Another valid coding pattern looks like:
+
+```
+for (;;)
+{
+	if (work to do)
+	{
+		/* in particular, exit the loop if some condition satisfied */
+		Do Stuff();
+	}
+	WaitInterrupt(INTERRUPT_WAIT_WAKEUP, ...);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+}
+```
+
+This is useful to reduce interrupt traffic if it's expected that the
+loop's termination condition will often be satisfied in the first
+iteration; the cost is an extra loop iteration before blocking when it
+is not.  What must be avoided is placing any checks for asynchronous
+events after WaitInterrupt and before ClearInterrupt, as that creates
+a race condition.
+
+Custom interrupts in extensions
+-------------------------------
+
+An extension can allocate interrupt bits for its own purposes by
+calling RequestAddinInterrupt(). Note that interrupts are a somewhat
+scarce resource, so consider using INTERRUPT_WAIT_WAKEUP if all you
+need is a simple wakeup in some loop.
+
+
+Unix Signals
+============
+
+Postmaster and most child processes also respond to a few standard
+Unix signals:
+
+SIGHUP -> Raises INTERRUPT_CONFIG_RELOAD
+SIGINT -> Raises INTERRUPT_QUERY_CANCEL
+SIGTERM -> Raises INTERRUPT_TERMINATE
+SIGQUIT -> immediate shutdown, abort the process
+
+pg_ctl uses these Unix signals to tell postmaster to reload config,
+stop, etc. These are also mentioned in the user documentation.
+
+Postmaster cannot send interrupts to processes without PGPROC entries
+(just syslogger nowadays), and it doesn't know the PGPROC entries of
+other child processes anyway. Hence, it still uses the above Unix
+signals for postmaster -> child signaling. The only exception is when
+postmaster notifies a backend that a bgworker it launched has
+exited. Postmaster sends that interrupt directly. The backend
+registers explicitly for that notification, and supplies the
+ProcNumber to postmaster when registering.
+
+There are a few more exceptions:
+
+- A few processes like the checkpointer, archiver, and IO workers
+  don't react to SIGTERM, because Unix system shutdown sends a SIGTERM
+  to all processes, and we want them to exit only only after all other
+  process. Instead, the postmaster sends them SIGUSR2 after all the
+  other processes have exited.
+
+- Child processes use SIGUSR1 to request the postmaster to do various
+  actions or notify that some event has occurred. See pmsignal.c for
+  details on that mechanism
+
+
+TODO: Open questions
+====================
+
+TODO: Put pendingInterrupts in PMChildSlot or PGPROC?
+----------------------------------------------------
+
+Postmaster cannot send interrupts to processes without PGPROC
+entries. Hence, it still uses plain Unix signals. Would be nice if
+postmaster could send interrupts directly. If we moved the interrupt
+mask to PMChildSlot, it could.  However, PGPROC seems like a more
+natural location otherwise.
+
+Options:
+
+a) Move pendingInterrupts to PMChildSlot. That way, you can send signals
+   to processes also to processes that don't have a PGPROC entry.
+
+b) Add a pendingInterrupts mask to PMChildSlot, but also have it in PGPROC.
+   When postmaster sens an interrupt, it can check if it has a PGPROC entry,
+   and if not, use the pendingInterrupts field in PMChildSlot instead.
+
+c) Assign a PGPROC entry for every child process in postmaster already.
+   (Except dead-end backends).
+
+d) Keep it as it is, continue to use signals for postmaster -> child
+   signaling
+
+
+TODO: Unique session id
+----------------------
+
+In some places, we read the pid of a process (from PGPROC or
+elsewhere), and send signal to it, accepting that the process may have
+already exited.  If we directly replace those uses of 'pid' with a
+ProcNumber, the ProcNumber might get reused much faster than the pid
+would. Solutions:
+
+a) Introduce the concept of a unique session ID that is never recycled
+   (or not for a long time, anyway). When reading the ProcNumber to
+   send an interrupt to, also read the session ID. When sending the
+   interrupt, check that the session ID matches.
+
+
+TODO: Other
+-----------
+
+- Check performance of CHECK_FOR_INTERRUPTS(). It's very cheap, but
+  it's nevertheless more instructions than it used to be.
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
new file mode 100644
index 00000000000..61d148409bc
--- /dev/null
+++ b/src/backend/ipc/interrupt.c
@@ -0,0 +1,431 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.c
+ *	  Inter-process interrupts.
+ *
+ *
+ * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/interrupt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "ipc/interrupt.h"
+#include "miscadmin.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "utils/resowner.h"
+
+/*
+ * Currently installed interrupt handlers
+ */
+static pg_interrupt_handler_t interrupt_handlers[64];
+
+/*
+ * XXX: is 'volatile' still needed on all the variables below? Which ones are
+ * accessed from signal handlers?
+ */
+
+/* Bitmask of currently enabled interrupts */
+volatile InterruptMask EnabledInterruptsMask;
+
+/*
+ * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
+ * equal to EnabledInterruptsMask, except when interrupts are held off by
+ * HOLD/RESUME_INTERRUPTS() or a critical section.
+ */
+volatile InterruptMask CheckForInterruptsMask;
+
+/* Variables for holdoff mechanism */
+volatile uint32 InterruptHoldoffCount = 0;
+volatile uint32 CritSectionCount = 0;
+
+/* A common WaitEventSet used to implement WaitInterrupt() */
+static WaitEventSet *InterruptWaitSet;
+
+/* The position of the interrupt in InterruptWaitSet. */
+#define InterruptWaitSetInterruptPos 0
+#define InterruptWaitSetPostmasterDeathPos 1
+
+static pg_atomic_uint64 LocalPendingInterrupts;
+
+pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+
+static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
+
+/*
+ * Install an interrupt handler callback function for the given interrupt.
+ *
+ * You need to also enable the interrupt with EnableInterrupt(), unless you're
+ * replacing an existing handler function.
+ */
+void
+SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler)
+{
+	/*
+	 * XXX: It's somewhat inefficient to loop through all the bits, but this
+	 * isn't performance critical.
+	 */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+		{
+			/* Replace old handler */
+			interrupt_handlers[i] = handler;
+		}
+	}
+}
+
+/* Enable an interrupt to be processed by CHECK_FOR_INTERRUPTS() */
+void
+EnableInterrupt(InterruptMask interruptMask)
+{
+#ifdef USE_ASSERT_CHECKING
+	/* Check that the interrupt has a handler defined */
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptMask & UINT64_BIT(i)) != 0)
+			Assert(interrupt_handlers[i] != NULL);
+	}
+#endif
+
+	EnabledInterruptsMask |= interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * Disable the handler function for an interrupt.
+ *
+ * When disabled, CHECK_FOR_INTERRUPTS() will not call the handler function
+ * for the given interrupt.  If the interrupt is received, it will remain
+ * pending until you manually check and clear it with ClearInterrupt(), or
+ * re-enable the handler function.
+ */
+void
+DisableInterrupt(InterruptMask interruptMask)
+{
+	EnabledInterruptsMask &= ~interruptMask;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+}
+
+/*
+ * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
+ *
+ * If an interrupt condition is pending, and it's safe to service it,
+ * then clear the flag and call the interrupt handler.
+ *
+ * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
+ * ProcessInterrupts is guaranteed to clear the given interrupt before
+ * returning, if it was set when entering.  (This is not the same as
+ * guaranteeing that it's still clear when we return; another interrupt could
+ * have arrived.  But we promise that any pre-existing one will have been
+ * serviced.)
+ */
+void
+ProcessInterrupts(void)
+{
+	InterruptMask interruptsToProcess;
+
+	Assert(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+
+	/* Check once what interrupts are pending */
+	interruptsToProcess =
+		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+
+	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	{
+		if ((interruptsToProcess & UINT64_BIT(i)) != 0)
+		{
+			/*
+			 * Clear the interrupt *before* calling the handler function, so
+			 * that if the interrupt is received again while the handler
+			 * function is being executed, we won't miss it.
+			 *
+			 * For similar reasons, we also clear the flags one by one even if
+			 * multiple interrupts are pending.  Otherwise if one of the
+			 * interrupt handlers bail out with an ERROR, we would have
+			 * already cleared the other bits, and would miss processing them.
+			 */
+			ClearInterrupt(UINT64_BIT(i));
+
+			/* Call the handler function */
+			(*interrupt_handlers[i]) ();
+		}
+	}
+}
+
+/*
+ * Switch to local interrupts.  Other backends can't send interrupts to this
+ * one.  Only RaiseInterrupt() can set them, from inside this process.
+ */
+void
+SwitchToLocalInterrupts(void)
+{
+	if (MyPendingInterrupts == &LocalPendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &LocalPendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/*
+	 * Mix in the interrupts that we have received already in our shared
+	 * interrupt vector, while atomically clearing it.  Other backends may
+	 * continue to set bits in it after this point, but we've atomically
+	 * transferred the existing bits to our local vector so we won't get
+	 * duplicated interrupts later if we switch back.
+	 */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+}
+
+/*
+ * Switch to shared memory interrupts.  Other backends can send interrupts to
+ * this one if they know its ProcNumber, and we'll now see any that we missed.
+ */
+void
+SwitchToSharedInterrupts(void)
+{
+	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
+		return;
+
+	MyPendingInterrupts = &MyProc->pendingInterrupts;
+
+	/*
+	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
+	 * seeing the new MyPendingInterrupts destination.
+	 */
+	pg_memory_barrier();
+
+	/* Mix in any unhandled bits from LocalPendingInterrupts. */
+	pg_atomic_fetch_or_u64(MyPendingInterrupts,
+						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+}
+
+/*
+ * Set an interrupt flag in this backend.
+ *
+ * Note: This is called from signal handlers, so needs to be async-signal
+ * safe!
+ */
+void
+RaiseInterrupt(InterruptMask interruptMask)
+{
+	uint64		old_pending;
+
+	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupMyProc();
+}
+
+/*
+ * Set an interrupt flag in another backend.
+ *
+ * Note: This can also be called from the postmaster, so be careful to not
+ * trust the contents of shared memory.
+ *
+ * FIXME: it's easy to accidentally swap the order of the args.  Could we have
+ * stricter type checking?
+ */
+void
+SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
+{
+	PGPROC	   *proc;
+	uint64		old_pending;
+
+	Assert(pgprocno != INVALID_PROC_NUMBER);
+	Assert(pgprocno >= 0);
+	Assert(pgprocno < ProcGlobal->allProcCount);
+
+	proc = &ProcGlobal->allProcs[pgprocno];
+	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
+
+	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
+
+	/*
+	 * If the process is currently blocked waiting for an interrupt to arrive,
+	 * and the interrupt wasn't already pending, wake it up.
+	 */
+	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+		WakeupOtherProc(proc);
+}
+
+void
+InitializeInterruptWaitSet(void)
+{
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
+
+	Assert(InterruptWaitSet == NULL);
+
+	/* Set up the WaitEventSet used by WaitInterrupt(). */
+	InterruptWaitSet = CreateWaitEventSet(NULL, 2);
+	interrupt_pos = AddWaitEventToSet(InterruptWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  0, NULL);
+	if (IsUnderPostmaster)
+		AddWaitEventToSet(InterruptWaitSet, WL_EXIT_ON_PM_DEATH,
+						  PGINVALID_SOCKET, 0, NULL);
+
+	Assert(interrupt_pos == InterruptWaitSetInterruptPos);
+}
+
+/*
+ * Wait for any of the interrupts in interruptMask to be set, or for
+ * postmaster death, or until timeout is exceeded. 'wakeEvents' is a bitmask
+ * that specifies which of those events to wait for. If the interrupt is
+ * already pending (and WL_INTERRUPT is given), the function returns
+ * immediately.
+ *
+ * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
+ * is given.  Although it is declared as "long", we don't actually support
+ * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
+ * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
+ *
+ * Returns bit mask indicating which condition(s) caused the wake-up. Note
+ * that if multiple wake-up conditions are true, there is no guarantee that
+ * we return all of them in one call, but we will return at least one.
+ */
+int
+WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+			  uint32 wait_event_info)
+{
+	WaitEvent	event;
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	/*
+	 * Some callers may have an interrupt mask different from last time, or no
+	 * interrupt mask at all, or want to handle postmaster death differently.
+	 * It's cheap to assign those, so just do it every time.
+	 */
+	if (!(wakeEvents & WL_INTERRUPT))
+		interruptMask = 0;
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetInterruptPos,
+					WL_INTERRUPT, interruptMask);
+
+	ModifyWaitEvent(InterruptWaitSet, InterruptWaitSetPostmasterDeathPos,
+					(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
+					0);
+
+	if (WaitEventSetWait(InterruptWaitSet,
+						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
+						 &event, 1,
+						 wait_event_info) == 0)
+		return WL_TIMEOUT;
+	else
+		return event.events;
+}
+
+/*
+ * Like WaitInterrupt, but with an extra socket argument for WL_SOCKET_*
+ * conditions.
+ *
+ * When waiting on a socket, EOF and error conditions always cause the socket
+ * to be reported as readable/writable/connected, so that the caller can deal
+ * with the condition.
+ *
+ * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
+ * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
+ * return value if the postmaster dies.  The latter is useful for rare cases
+ * where some behavior other than immediate exit is needed.
+ *
+ * NB: These days this is just a wrapper around the WaitEventSet API. When
+ * using an interrupt very frequently, consider creating a longer living
+ * WaitEventSet instead; that's more efficient.
+ */
+int
+WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+					  long timeout, uint32 wait_event_info)
+{
+	int			ret;
+	int			rc;
+	WaitEvent	event;
+	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
+
+	if (wakeEvents & WL_TIMEOUT)
+		Assert(timeout >= 0);
+	else
+		timeout = -1;
+
+	if (wakeEvents & WL_INTERRUPT)
+		AddWaitEventToSet(set, WL_INTERRUPT, PGINVALID_SOCKET,
+						  interruptMask, NULL);
+
+	/* Postmaster-managed callers must handle postmaster death somehow. */
+	Assert(!IsUnderPostmaster ||
+		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
+		   (wakeEvents & WL_POSTMASTER_DEATH));
+
+	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
+		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
+						  0, NULL);
+
+	if (wakeEvents & WL_SOCKET_MASK)
+	{
+		int			ev;
+
+		ev = wakeEvents & WL_SOCKET_MASK;
+		AddWaitEventToSet(set, ev, sock, 0, NULL);
+	}
+
+	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
+	if (rc == 0)
+		ret = WL_TIMEOUT;
+	else
+	{
+		ret = event.events & (WL_INTERRUPT |
+							  WL_POSTMASTER_DEATH |
+							  WL_SOCKET_MASK);
+	}
+
+	FreeWaitEventSet(set);
+
+	return ret;
+}
+
+/*
+ * This is used as the INTERRUPT_TERMINATE handler in some aux processes that
+ * want to just exit immediately.
+ */
+void
+ProcessAuxProcessShutdownInterrupt(void)
+{
+	proc_exit(0);
+}
+
+/* Reserve an interrupt bit for use in an extension */
+InterruptMask
+RequestAddinInterrupt(void)
+{
+	InterruptMask result;
+
+	if (nextAddinInterruptBit == END_ADDIN_INTERRUPTS)
+		elog(ERROR, "out of addin interrupt bits");
+
+	result = UINT64_BIT(nextAddinInterruptBit);
+	nextAddinInterruptBit++;
+	return result;
+}
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..1f698bd0c68
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,6 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'interrupt.c',
+  'signal_handlers.c',
+)
diff --git a/src/backend/ipc/signal_handlers.c b/src/backend/ipc/signal_handlers.c
new file mode 100644
index 00000000000..589f755622a
--- /dev/null
+++ b/src/backend/ipc/signal_handlers.c
@@ -0,0 +1,160 @@
+/*-------------------------------------------------------------------------
+ *
+ * signal_handlers.c
+ *	  Standard signal handlers.
+ *
+ * These just raise the corresponding INTERRUPT_* flags:
+ *
+ * SIGHUP ->  request config reload
+ * SIGINT == query cancel
+ * SIGTERM == graceful terminate of the process
+ * SIGQUIT == exit immediately (causes crash restart)
+ *
+ * XXX: We should not send signals directly between processes anymore. Use
+ * SendInterrupt instead.
+ *
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/backend/ipc/signal_handlers.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <unistd.h>
+
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
+#include "libpq/pqsignal.h"
+
+/*
+ * Set the standard signal handlers suitable for most postmaster child
+ * processes.
+ *
+ * Note: this doesn't unblock the signals yet. You can make additional
+ * pqsignal() calls to modify the default behavior before unblocking.
+ */
+void
+SetPostmasterChildSignalHandlers(void)
+{
+	/*----------
+	 * These raise the corresponding interrupts in the process:
+	 *
+	 * SIGHUP -> INTERRUPT_CONFIG_RELOAD
+	 * SIGINT -> INTERRUPT_QUERY_CANCEL
+	 * SIGTERM -> INTERRUPT_TERMINATE
+	 *
+	 * The process may ignore the interrupts that these raise, e.g if query
+	 * cancellation is not applicable.  But there's no harm in having the
+	 * signal handlers in place anyway.
+	 */
+	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	pqsignal(SIGINT, SignalHandlerForQueryCancel);
+	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
+	pqsignal(SIGPIPE, SIG_IGN);
+	pqsignal(SIGUSR1, SIG_IGN);
+
+	/*
+	 * SIGUSR2 is sent by postmaster to some aux processes, for different
+	 * purposes.  Such processes override this before unblocking signals, but
+	 * ignore it by default.
+	 */
+	pqsignal(SIGUSR2, SIG_IGN);
+
+	/* FIXME: should we do this in all processes? */
+	/* pqsignal(SIGFPE, FloatExceptionHandler); */
+
+	/*
+	 * SIGALRM is used for timeouts, but the handler is established later in
+	 * InitializeTimeouts()
+	 */
+	pqsignal(SIGALRM, SIG_IGN);
+
+	/* SIGURG is handled in waiteventset.c */
+
+	/*
+	 * Reset signals that are used by postmaster but not by child processes.
+	 *
+	 * Currently just SIGCHLD.  The handlers for other signals are overridden
+	 * later, depending on the child process type.
+	 */
+	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
+								 * than SIG_IGN on some platforms */
+
+	/*
+	 * Every postmaster child process is expected to respond promptly to
+	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
+	 * BlockSig and install a suitable signal handler.  (Client-facing
+	 * processes may choose to replace this default choice of handler with
+	 * quickdie().)  All other blockable signals remain blocked for now.
+	 */
+	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
+
+	sigdelset(&BlockSig, SIGQUIT);
+	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+}
+
+/*
+ * Simple signal handler for triggering a configuration reload.
+ *
+ * Normally, this handler would be used for SIGHUP. The idea is that code
+ * which uses it would arrange to check the INTERRUPT_CONFIG_RELOAD interrupt
+ * at convenient places inside main loops.
+ */
+void
+SignalHandlerForConfigReload(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_CONFIG_RELOAD);
+}
+
+/*
+ * Simple signal handler for exiting quickly as if due to a crash.
+ *
+ * Normally, this would be used for handling SIGQUIT.
+ */
+void
+SignalHandlerForCrashExit(SIGNAL_ARGS)
+{
+	/*
+	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
+	 * because shared memory may be corrupted, so we don't want to try to
+	 * clean up our transaction.  Just nail the windows shut and get out of
+	 * town.  The callbacks wouldn't be safe to run from a signal handler,
+	 * anyway.
+	 *
+	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
+	 * a system reset cycle if someone sends a manual SIGQUIT to a random
+	 * backend.  This is necessary precisely because we don't clean up our
+	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
+	 * should ensure the postmaster sees this as a crash, too, but no harm in
+	 * being doubly sure.)
+	 */
+	_exit(2);
+}
+
+/*
+ * Simple signal handler for triggering a long-running process to shut down
+ * and exit.
+ *
+ * In most processes, this handler is used for SIGTERM, but some processes use
+ * other signals.
+ */
+void
+SignalHandlerForShutdownRequest(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/*
+ * Query-cancel signal: abort current transaction at soonest convenient time
+ */
+void
+SignalHandlerForQueryCancel(SIGNAL_ARGS)
+{
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index e04aa2e68ed..f8fbdd090d4 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -1673,8 +1673,9 @@ interpret_ident_response(const char *ident_response,
  *	owns the tcp connection to "local_addr"
  *	If the username is successfully retrieved, check the usermap.
  *
- *	XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if the
- *	latch was set would improve the responsiveness to timeouts/cancellations.
+ *	XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+ *	if the interrupt was pending would improve the responsiveness to
+ *	timeouts/cancellations.
  */
 static int
 ident_inet(Port *port)
@@ -3108,8 +3109,8 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
 	 * packets to our port thus causing us to retry in a loop and never time
 	 * out.
 	 *
-	 * XXX: Using WaitLatchOrSocket() and doing a CHECK_FOR_INTERRUPTS() if
-	 * the latch was set would improve the responsiveness to
+	 * XXX: Using WaitInterruptOrSocket() and doing a CHECK_FOR_INTERRUPTS()
+	 * if the interrupt was pending would improve the responsiveness to
 	 * timeouts/cancellations.
 	 */
 	gettimeofday(&endtime, NULL);
diff --git a/src/backend/libpq/be-secure-gssapi.c b/src/backend/libpq/be-secure-gssapi.c
index 5fb043b8608..1a60ca8f78a 100644
--- a/src/backend/libpq/be-secure-gssapi.c
+++ b/src/backend/libpq/be-secure-gssapi.c
@@ -15,13 +15,13 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/be-gssapi-common.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bswap.h"
-#include "storage/latch.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 
@@ -422,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.
  *
@@ -458,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
@@ -497,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
@@ -679,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 4da6ac22ff9..3a0ff956823 100644
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -28,11 +28,12 @@
 #include <arpa/inet.h>
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
+#include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
 
@@ -533,8 +534,8 @@ aloop:
 				else
 					waitfor = WL_SOCKET_WRITEABLE | WL_EXIT_ON_PM_DEATH;
 
-				(void) WaitLatchOrSocket(NULL, waitfor, port->sock, 0,
-										 WAIT_EVENT_SSL_OPEN_SERVER);
+				(void) WaitInterruptOrSocket(0, waitfor, port->sock, 0,
+											 WAIT_EVENT_SSL_OPEN_SERVER);
 				goto aloop;
 			case SSL_ERROR_SYSCALL:
 				if (r < 0 && errno != 0)
diff --git a/src/backend/libpq/be-secure.c b/src/backend/libpq/be-secure.c
index edd69823b92..8abafa649b7 100644
--- a/src/backend/libpq/be-secure.c
+++ b/src/backend/libpq/be-secure.c
@@ -27,9 +27,8 @@
 #include <netinet/tcp.h>
 #include <arpa/inet.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
 #include "utils/wait_event.h"
@@ -175,6 +174,9 @@ secure_close(Port *port)
 
 /*
  *	Read data from a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_read(Port *port, void *ptr, size_t len)
@@ -182,9 +184,6 @@ secure_read(Port *port, void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientReadInterrupt(false);
-
 retry:
 #ifdef USE_SSL
 	waitfor = 0;
@@ -214,7 +213,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_READ);
@@ -229,23 +229,22 @@ retry:
 		 * new connections can be accepted.  Exiting clears the deck for a
 		 * postmaster restart.
 		 *
-		 * (Note that we only make this check when we would otherwise sleep on
-		 * our latch.  We might still continue running for a while if the
-		 * postmaster is killed in mid-query, or even through multiple queries
-		 * if we never have to wait for read.  We don't want to burn too many
-		 * cycles checking for this very rare condition, and this should cause
-		 * us to exit quickly in most cases.)
+		 * (Note that we only make this check when we would otherwise sleep
+		 * waiting for interrupt.  We might still continue running for a while
+		 * if the postmaster is killed in mid-query, or even through multiple
+		 * queries if we never have to wait for read.  We don't want to burn
+		 * too many cycles checking for this very rare condition, and this
+		 * should cause us to exit quickly in most cases.)
 		 */
 		if (event.events & WL_POSTMASTER_DEATH)
 			ereport(FATAL,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientReadInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the read. Most likely it will return immediately
@@ -256,12 +255,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) read.
-	 */
-	ProcessClientReadInterrupt(false);
-
 	return n;
 }
 
@@ -285,7 +278,8 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 	/*
 	 * Try to read from the socket without blocking. If it succeeds we're
-	 * done, otherwise we'll wait for the socket using the latch mechanism.
+	 * done, otherwise we'll wait for the socket using the interrupt
+	 * mechanism.
 	 */
 #ifdef WIN32
 	pgwin32_noblock = true;
@@ -301,6 +295,9 @@ secure_raw_read(Port *port, void *ptr, size_t len)
 
 /*
  *	Write data to a secure connection.
+ *
+ * In blocking mode, will process any interrupts that arrive while we're
+ * waiting.
  */
 ssize_t
 secure_write(Port *port, const void *ptr, size_t len)
@@ -308,9 +305,6 @@ secure_write(Port *port, const void *ptr, size_t len)
 	ssize_t		n;
 	int			waitfor;
 
-	/* Deal with any already-pending interrupt condition. */
-	ProcessClientWriteInterrupt(false);
-
 retry:
 	waitfor = 0;
 #ifdef USE_SSL
@@ -339,7 +333,8 @@ retry:
 
 		Assert(waitfor);
 
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, NULL);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, waitfor, 0);
+		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, CheckForInterruptsMask);
 
 		WaitEventSetWait(FeBeWaitSet, -1 /* no timeout */ , &event, 1,
 						 WAIT_EVENT_CLIENT_WRITE);
@@ -350,11 +345,10 @@ retry:
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to unexpected postmaster exit")));
 
-		/* Handle interrupt. */
-		if (event.events & WL_LATCH_SET)
+		/* Handle interrupts. */
+		if (event.events & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
-			ProcessClientWriteInterrupt(true);
+			CHECK_FOR_INTERRUPTS();
 
 			/*
 			 * We'll retry the write. Most likely it will return immediately
@@ -365,12 +359,6 @@ retry:
 		goto retry;
 	}
 
-	/*
-	 * Process interrupts that happened during a successful (or non-blocking,
-	 * or hard-failed) write.
-	 */
-	ProcessClientWriteInterrupt(false);
-
 	return n;
 }
 
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 4a442f22df6..ff4b04f9e10 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,12 +73,11 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "utils/guc_hooks.h"
 #include "utils/memutils.h"
 
@@ -176,7 +175,7 @@ pq_init(ClientSocket *client_sock)
 {
 	Port	   *port;
 	int			socket_pos PG_USED_FOR_ASSERTS_ONLY;
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
+	int			interrupt_pos PG_USED_FOR_ASSERTS_ONLY;
 
 	/* allocate the Port struct and copy the ClientSocket contents to it */
 	port = palloc0_object(Port);
@@ -288,8 +287,8 @@ pq_init(ClientSocket *client_sock)
 
 	/*
 	 * In backends (as soon as forked) we operate the underlying socket in
-	 * nonblocking mode and use latches to implement blocking semantics if
-	 * needed. That allows us to provide safely interruptible reads and
+	 * nonblocking mode and use WaitEventSet to implement blocking semantics
+	 * if needed. That allows us to provide safely interruptible reads and
 	 * writes.
 	 */
 #ifndef WIN32
@@ -307,18 +306,18 @@ pq_init(ClientSocket *client_sock)
 
 	FeBeWaitSet = CreateWaitEventSet(NULL, FeBeWaitSetNEvents);
 	socket_pos = AddWaitEventToSet(FeBeWaitSet, WL_SOCKET_WRITEABLE,
-								   port->sock, NULL, NULL);
-	latch_pos = AddWaitEventToSet(FeBeWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
+								   port->sock, 0, NULL);
+	interrupt_pos = AddWaitEventToSet(FeBeWaitSet, WL_INTERRUPT, PGINVALID_SOCKET,
+									  CheckForInterruptsMask, NULL);
 	AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-					  NULL, NULL);
+					  0, NULL);
 
 	/*
 	 * The event positions match the order we added them, but let's sanity
 	 * check them to be sure.
 	 */
 	Assert(socket_pos == FeBeWaitSetSocketPos);
-	Assert(latch_pos == FeBeWaitSetLatchPos);
+	Assert(interrupt_pos == FeBeWaitSetInterruptPos);
 
 	return port;
 }
@@ -1412,8 +1411,7 @@ internal_flush_buffer(const char *buf, size_t *start, size_t *end)
 			 * the connection.
 			 */
 			*start = *end = 0;
-			ClientConnectionLost = 1;
-			InterruptPending = 1;
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
 			return EOF;
 		}
 
@@ -2063,25 +2061,14 @@ pq_check_connection(void)
 	 * It's OK to modify the socket event filter without restoring, because
 	 * all FeBeWaitSet socket wait sites do the same.
 	 */
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, WL_SOCKET_CLOSED, NULL);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, WL_SOCKET_CLOSED, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, 0);
 
-retry:
 	rc = WaitEventSetWait(FeBeWaitSet, 0, events, lengthof(events), 0);
 	for (int i = 0; i < rc; ++i)
 	{
 		if (events[i].events & WL_SOCKET_CLOSED)
 			return false;
-		if (events[i].events & WL_LATCH_SET)
-		{
-			/*
-			 * A latch event might be preventing other events from being
-			 * reported.  Reset it and poll again.  No need to restore it
-			 * because no code should expect latches to survive across
-			 * CHECK_FOR_INTERRUPTS().
-			 */
-			ResetLatch(MyLatch);
-			goto retry;
-		}
 	}
 
 	return true;
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index 7e4a725b796..0a029eb02a8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -14,19 +14,18 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 
 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);
@@ -76,14 +75,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;
 }
 
@@ -170,27 +168,19 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		Assert(pq_mq_handle != NULL);
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
-		if (pq_mq_parallel_leader_pid != 0)
+		if (pq_mq_parallel_leader_proc_number != INVALID_PROC_NUMBER)
 		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
+			SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+						  pq_mq_parallel_leader_proc_number);
 		}
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
 
-		(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-						 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+							 WAIT_EVENT_MESSAGE_QUEUE_PUT_MESSAGE);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		CHECK_FOR_INTERRUPTS();
 	}
 
diff --git a/src/backend/meson.build b/src/backend/meson.build
index 4f5292d8f88..a438a2b575d 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 0f4435d2d97..729a0bcbbe9 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -19,7 +19,6 @@ OBJS = \
 	bgwriter.o \
 	checkpointer.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 0a730fb5b45..5763057a87a 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -27,15 +27,17 @@
  * launcher has set up.
  *
  * If the fork() call fails in the postmaster, it sets a flag in the shared
- * memory area, and sends a signal to the launcher.  The launcher, upon
- * noticing the flag, can try starting the worker again by resending the
+ * memory area, sends a signal to the launcher, raising the
+ * INTERRUPT_AUTOVACUUM_WORKER_FINISHED interrupt. The launcher, upon
+ * noticing the interrupt, can try starting the worker again by resending the
  * signal.  Note that the failure can only be transient (fork failure due to
  * high load, memory pressure, too many processes, etc); more permanent
  * problems, like failure to connect to a database, are detected later in the
  * worker and dealt with just by having the worker exit normally.  The launcher
  * will launch a new worker again later, per schedule.
  *
- * When the worker is done vacuuming it sends SIGUSR2 to the launcher.  The
+ * When the worker is done vacuuming and exits, the postmaster sends SIGUSR2
+ * to the launcher, raising INTERRUPT_AUTOVACUUM_WORKER_FINISHED.  The
  * launcher then wakes up and is able to launch another worker, if the schedule
  * is so tight that a new worker is needed immediately.  At this time the
  * launcher can also balance the settings for the various remaining workers'
@@ -79,18 +81,18 @@
 #include "catalog/pg_namespace.h"
 #include "commands/vacuum.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -151,9 +153,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;
@@ -287,6 +286,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
@@ -322,7 +324,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);
@@ -394,21 +396,23 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	Assert(GetProcessingMode() == InitProcessing);
 
 	/*
-	 * Set up signal handlers.  We operate on databases much like a regular
-	 * backend, so we use the same signal handling.  See equivalent code in
-	 * tcop/postgres.c.
+	 * Set up signal and interrupt handlers.  We operate on databases much
+	 * like a regular backend, so we use mostly the same handling.  See
+	 * equivalent code in tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-
+	SetStandardInterruptHandlers();
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+	/* Postmaster uses SIGUSR2 to raise INTERRUPT_AUTOVACUUM_WORKER_FINISHED */
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
+	SetInterruptHandler(INTERRUPT_TERMINATE, AutoVacLauncherShutdown);
+
+	/* We can safely reload config at any CHECK_FOR_INTERRUPTS() */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessAutoVacLauncherConfigReloadInterrupt);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -455,7 +459,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();
@@ -497,7 +502,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();
 
 		/*
@@ -556,7 +561,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 */
 	}
@@ -571,41 +576,44 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	rebuild_database_list(InvalidOid);
 
 	/* loop until shutdown request */
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		struct timeval nap;
 		TimestampTz current_time = 0;
 		bool		can_launch;
 
 		/*
-		 * This loop is a bit different from the normal use of WaitLatch,
+		 * This loop is a bit different from the normal use of WaitInterrupt,
 		 * because we'd like to sleep before the first launch of a child
-		 * process.  So it's WaitLatch, then ResetLatch, then check for
-		 * wakening conditions.
+		 * process.  So it's WaitInterrupt, then ClearInterrupt, then check
+		 * for wakening conditions.
 		 */
 
 		launcher_determine_sleep(av_worker_available(), false, &nap);
 
 		/*
-		 * Wait until naptime expires or we get some type of signal (all the
-		 * signal handlers will wake us by calling SetLatch).
+		 * Wait until naptime expires or we get some type of interrupt.
 		 */
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
-						 WAIT_EVENT_AUTOVACUUM_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask |
+							 INTERRUPT_WAIT_WAKEUP |
+							 INTERRUPT_AUTOVACUUM_WORKER_FINISHED,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L),
+							 WAIT_EVENT_AUTOVACUUM_MAIN);
 
-		ResetLatch(MyLatch);
+		CHECK_FOR_INTERRUPTS();
 
-		ProcessAutoVacLauncherInterrupts();
+		/*
+		 * FIXME: is this still needed? Does anyone send INTERRUPT_WAIT_WAKEUP
+		 * to av launcher?
+		 */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * a worker finished, or postmaster signaled failure to start a worker
 		 */
-		if (got_SIGUSR2)
+		if (ConsumeInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED))
 		{
-			got_SIGUSR2 = false;
-
 			/* rebalance cost limits, if needed */
 			if (AutoVacuumShmem->av_signal[AutoVacRebalance])
 			{
@@ -741,21 +749,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? */
@@ -773,17 +777,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();
 }
 
 /*
@@ -1350,8 +1343,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)
@@ -1363,8 +1356,7 @@ AutoVacWorkerFailed(void)
 static void
 avl_sigusr2_handler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_AUTOVACUUM_WORKER_FINISHED);
 }
 
 
@@ -1399,22 +1391,18 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 * backend, so we use the same signal handling.  See equivalent code in
 	 * tcop/postgres.c.
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-
-	/*
-	 * 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, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGFPE, FloatExceptionHandler);
+	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
@@ -1545,12 +1533,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
 	{
@@ -2292,9 +2275,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);
 
 			/*
@@ -2452,7 +2434,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();
 		{
@@ -2541,9 +2523,8 @@ deleted:
 		 * Check for config changes before acquiring lock for further jobs.
 		 */
 		CHECK_FOR_INTERRUPTS();
-		if (ConfigReloadPending)
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 		{
-			ConfigReloadPending = false;
 			ProcessConfigFile(PGC_SIGHUP);
 			VacuumUpdateCosts();
 		}
@@ -2665,7 +2646,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 8ea800c0bbd..ee16e9cb025 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -15,7 +15,6 @@
 #include <unistd.h>
 #include <signal.h>
 
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
@@ -23,6 +22,7 @@
 #include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
+#include "utils/resowner.h"
 #include "utils/ps_status.h"
 
 
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 133af5aee97..20b7ba737ba 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -13,6 +13,8 @@
 #include "postgres.h"
 
 #include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -22,12 +24,10 @@
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/ascii.h"
@@ -79,6 +79,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;
 
 /*
@@ -202,8 +204,9 @@ BackgroundWorkerShmemInit(void)
 			slot->terminate = false;
 			slot->pid = InvalidPid;
 			slot->generation = 0;
+			slot->notify_pmchild = 0;
+			slot->notify_proc_number = INVALID_PROC_NUMBER;
 			rw->rw_shmem_slot = slotno;
-			rw->rw_worker.bgw_notify_pid = 0;	/* might be reinit after crash */
 			memcpy(&slot->worker, &rw->rw_worker, sizeof(BackgroundWorker));
 			++slotno;
 		}
@@ -244,6 +247,19 @@ FindRegisteredWorkerBySlotNumber(int slotno)
 	return NULL;
 }
 
+ProcNumber
+GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw)
+{
+	BackgroundWorkerSlot *slot = &BackgroundWorkerData->slot[rw->rw_shmem_slot];
+
+	/*
+	 * FIXME: be extra paranoid and sanity check the proc number, since this
+	 * runs in the postmaster
+	 */
+
+	return slot->notify_proc_number;
+}
+
 /*
  * Notice changes to shared memory made by other backends.
  * Accept new worker requests only if allow_new_workers is true.
@@ -325,20 +341,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;
@@ -346,8 +362,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;
 		}
@@ -393,23 +409,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;
@@ -431,7 +430,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.
  */
@@ -476,8 +475,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);
 }
 
 /*
@@ -493,12 +492,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
@@ -511,27 +510,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;
+		}
 	}
 }
 
@@ -563,14 +569,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);
 		}
 	}
 }
@@ -624,11 +630,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;
 		}
 	}
 }
@@ -756,32 +757,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, SIG_IGN);
-		pqsignal(SIGUSR1, SIG_IGN);
 		pqsignal(SIGFPE, SIG_IGN);
 	}
-	pqsignal(SIGTERM, die);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGHUP, SIG_IGN);
 
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR2, SIG_IGN);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 *
@@ -987,15 +984,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
@@ -1111,6 +1099,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.
@@ -1211,8 +1218,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)
@@ -1232,9 +1240,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)
 		{
@@ -1242,7 +1250,7 @@ WaitForBackgroundWorkerStartup(BackgroundWorkerHandle *handle, pid_t *pidp)
 			break;
 		}
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 	}
 
 	return status;
@@ -1256,8 +1264,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)
@@ -1275,9 +1284,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)
 		{
@@ -1285,7 +1294,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 dbd670c16cc..a53596d49ef 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,12 +32,13 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,11 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals that might be sent to us.
+	 * Set up interrupt handling
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/*
 	 * We just started, assume there has been either a shutdown or
@@ -220,9 +216,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.
@@ -295,22 +291,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
@@ -325,10 +321,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 978a7d0e649..054872aabce 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -11,8 +11,10 @@
  * condition.)
  *
  * The normal termination sequence is that checkpointer is instructed to
- * execute the shutdown checkpoint by SIGINT.  After that checkpointer waits
- * to be terminated via SIGUSR2, which instructs the checkpointer to exit(0).
+ * execute the shutdown checkpoint by SIGINT, which raises the
+ * INTERRUPT_SHUTDOWN_XLOG interrupt.  After that checkpointer waits
+ * to be terminated via SIGUSR2, raising INTERRUP_TERMINATE, which instructs
+ * the checkpointer to exit(0).
  * All backends must be stopped before SIGINT or SIGUSR2 is issued!
  *
  * Emergency termination is by SIGQUIT; like any backend, the checkpointer
@@ -44,13 +46,14 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -85,10 +88,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
@@ -163,7 +167,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;
@@ -175,7 +178,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);
@@ -205,22 +208,28 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	CheckpointerShmem->checkpointer_pid = MyProcPid;
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
-	 *
-	 * Note: we deliberately ignore SIGTERM, because during a standard Unix
-	 * system shutdown cycle, init will SIGTERM all processes at once.  We
-	 * want to wait for the backends to exit, whereupon the postmaster will
-	 * tell us it's okay to shut down (via SIGUSR2).
+	 * Ignore SIGTERM, because during a standard Unix system shutdown cycle,
+	 * init will SIGTERM all processes at once.  We want to wait for the
+	 * backends to exit, whereupon the postmaster will tell us it's okay to
+	 * shut down (via INTERRUPT_SHUTDOWN_XLOG).
+	 */
+	pqsignal(SIGTERM, SIG_IGN);
+
+	/*
+	 * Postmaster uses SIGINT to send us INTERRUPT_SHUTDOWN_XLOG, and SIGUSR2
+	 * for INTERRUP_TERMINATE
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
 	pqsignal(SIGINT, ReqShutdownXLOG);
-	pqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	/* Set up interrupt handling */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessCheckpointerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
+	/* we will check for INTERRUPT_TERMINATE explicitly in the loop */
+	DisableInterrupt(INTERRUPT_TERMINATE);
+
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
@@ -359,15 +368,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;
 
 		/*
@@ -541,10 +550,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;
 		}
 
@@ -569,7 +578,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);
@@ -585,10 +594,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);
 	}
 
 	/*
@@ -597,7 +609,7 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	ExitOnAnyError = true;
 
-	if (ShutdownXLOGPending)
+	if (InterruptPending(INTERRUPT_SHUTDOWN_XLOG))
 	{
 		/*
 		 * Close down the database.
@@ -615,7 +627,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);
 	}
 
 	/*
@@ -625,55 +637,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();
 }
 
 /*
@@ -785,24 +780,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;
 
@@ -817,10 +806,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)
 	{
@@ -832,10 +823,6 @@ CheckpointWriteDelay(int flags, double progress)
 		AbsorbSyncRequests();
 		absorb_counter = WRITES_PER_ABSORB;
 	}
-
-	/* Check for barrier events. */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
 }
 
 /*
@@ -924,12 +911,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);
 }
 
 
@@ -1102,14 +1088,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++)
@@ -1128,7 +1115,7 @@ RequestCheckpoint(int flags)
 		}
 		else
 		{
-			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 			/* notified successfully */
 			break;
 		}
@@ -1259,7 +1246,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;
@@ -1540,5 +1527,5 @@ WakeupCheckpointer(void)
 	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, checkpointerProc);
 }
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
deleted file mode 100644
index a2c0ff012c5..00000000000
--- a/src/backend/postmaster/interrupt.c
+++ /dev/null
@@ -1,108 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * interrupt.c
- *	  Interrupt handling routines.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
- *
- *-------------------------------------------------------------------------
- */
-
-#include "postgres.h"
-
-#include <unistd.h>
-
-#include "miscadmin.h"
-#include "postmaster/interrupt.h"
-#include "storage/ipc.h"
-#include "storage/latch.h"
-#include "storage/procsignal.h"
-#include "utils/guc.h"
-#include "utils/memutils.h"
-
-volatile sig_atomic_t ConfigReloadPending = false;
-volatile sig_atomic_t ShutdownRequestPending = false;
-
-/*
- * Simple interrupt handler for main loops of background processes.
- */
-void
-ProcessMainLoopInterrupts(void)
-{
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ConfigReloadPending)
-	{
-		ConfigReloadPending = false;
-		ProcessConfigFile(PGC_SIGHUP);
-	}
-
-	if (ShutdownRequestPending)
-		proc_exit(0);
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-}
-
-/*
- * Simple signal handler for triggering a configuration reload.
- *
- * Normally, this handler would be used for SIGHUP. The idea is that code
- * which uses it would arrange to check the ConfigReloadPending flag at
- * convenient places inside main loops, or else call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForConfigReload(SIGNAL_ARGS)
-{
-	ConfigReloadPending = true;
-	SetLatch(MyLatch);
-}
-
-/*
- * Simple signal handler for exiting quickly as if due to a crash.
- *
- * Normally, this would be used for handling SIGQUIT.
- */
-void
-SignalHandlerForCrashExit(SIGNAL_ARGS)
-{
-	/*
-	 * We DO NOT want to run proc_exit() or atexit() callbacks -- we're here
-	 * because shared memory may be corrupted, so we don't want to try to
-	 * clean up our transaction.  Just nail the windows shut and get out of
-	 * town.  The callbacks wouldn't be safe to run from a signal handler,
-	 * anyway.
-	 *
-	 * Note we do _exit(2) not _exit(0).  This is to force the postmaster into
-	 * a system reset cycle if someone sends a manual SIGQUIT to a random
-	 * backend.  This is necessary precisely because we don't clean up our
-	 * shared memory state.  (The "dead man switch" mechanism in pmsignal.c
-	 * should ensure the postmaster sees this as a crash, too, but no harm in
-	 * being doubly sure.)
-	 */
-	_exit(2);
-}
-
-/*
- * Simple signal handler for triggering a long-running background process to
- * shut down and exit.
- *
- * Typically, this handler would be used for SIGTERM, but some processes use
- * other signals. In particular, the checkpointer and parallel apply worker
- * exit on SIGUSR2, and the WAL writer exits on either SIGINT or SIGTERM.
- *
- * ShutdownRequestPending should be checked at a convenient place within the
- * main loop, or else the main loop should call ProcessMainLoopInterrupts.
- */
-void
-SignalHandlerForShutdownRequest(SIGNAL_ARGS)
-{
-	ShutdownRequestPending = true;
-	SetLatch(MyLatch);
-}
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index e1f70726604..fbd40cb10da 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'bgwriter.c',
   'checkpointer.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 10a5ef9e15c..1f4c3576e5b 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,17 +33,17 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -132,11 +132,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
  * ----------
@@ -148,10 +143,11 @@ static bool pgarch_archiveXlog(char *xlog);
 static bool pgarch_readyXlog(char *xlog);
 static void pgarch_archiveDone(char *xlog);
 static void pgarch_die(int code, Datum arg);
-static void ProcessPgArchInterrupts(void);
 static int	ready_file_comparator(Datum a, Datum b, void *arg);
 static void LoadArchiveLibrary(void);
 static void pgarch_call_module_shutdown_cb(int code, Datum arg);
+static void pgarch_ProcessConfigReloadInterrupt(void);
+
 
 /* Report shared memory space needed by PgArchShmemInit */
 Size
@@ -225,18 +221,27 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Ignore all signals usually bound to some action in the postmaster,
-	 * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT.
+	 * no query cancel. XXX: should we leave the signal handler in place and
+	 * just not install an interrupt handler for it?
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* an interrupt handler for it? */
 	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
+
+	/* TODO: use interrupt for this directly */
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
+	/*
+	 * These interrupt handlers are called in the loops pgarch_MainLoop and
+	 * pgarch_ArchiverCopyLoop. Do not set INTERRUPT_TERMINATE handler,
+	 * because that's different between the loops.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, pgarch_ProcessConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -247,8 +252,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;
 
@@ -282,13 +287,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);
 }
 
 
@@ -297,8 +301,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);
 }
 
 /*
@@ -309,7 +312,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
@@ -318,13 +321,15 @@ pgarch_MainLoop(void)
 	 */
 	do
 	{
-		ResetLatch(MyLatch);
+		/* FIXME: is this used for something in pgarch? To nudge it? */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* When we get SIGUSR2, we do one more archive cycle, then exit */
-		time_to_stop = ready_to_stop;
+		if (InterruptPending(INTERRUPT_SHUTDOWN_PGARCH))
+			time_to_stop = true;
 
 		/* Check for barrier events and config update */
-		ProcessPgArchInterrupts();
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * If we've gotten SIGTERM, we normally just sit and do nothing until
@@ -334,7 +339,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);
 
@@ -346,6 +351,7 @@ pgarch_MainLoop(void)
 		}
 
 		/* Do what we're here for */
+
 		pgarch_ArchiverCopyLoop();
 
 		/*
@@ -356,10 +362,13 @@ pgarch_MainLoop(void)
 		{
 			int			rc;
 
-			rc = WaitLatch(MyLatch,
-						   WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
-						   PGARCH_AUTOWAKE_INTERVAL * 1000L,
-						   WAIT_EVENT_ARCHIVER_MAIN);
+			rc = WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+							   INTERRUPT_TERMINATE |
+							   INTERRUPT_SHUTDOWN_PGARCH |
+							   CheckForInterruptsMask,
+							   WL_INTERRUPT | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+							   PGARCH_AUTOWAKE_INTERVAL * 1000L,
+							   WAIT_EVENT_ARCHIVER_MAIN);
 			if (rc & WL_POSTMASTER_DEATH)
 				time_to_stop = true;
 		}
@@ -408,7 +417,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;
 
 			/*
@@ -416,7 +425,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;
@@ -849,30 +858,13 @@ pgarch_die(int code, Datum arg)
 	PgArch->pgprocno = INVALID_PROC_NUMBER;
 }
 
-/*
- * Interrupt handler for WAL archiver process.
- *
- * This is called in the loops pgarch_MainLoop and pgarch_ArchiverCopyLoop.
- * It checks for barrier events, config update and request for logging of
- * memory contexts, but not shutdown request because how to handle
- * shutdown request is different between those loops.
- */
 static void
-ProcessPgArchInterrupts(void)
+pgarch_ProcessConfigReloadInterrupt(void)
 {
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ConfigReloadPending)
 	{
 		char	   *archiveLib = pstrdup(XLogArchiveLibrary);
 		bool		archiveLibChanged;
 
-		ConfigReloadPending = false;
 		ProcessConfigFile(PGC_SIGHUP);
 
 		if (XLogArchiveLibrary[0] != '\0' && XLogArchiveCommand[0] != '\0')
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 3fac46c402b..6efc5696996 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -539,10 +539,8 @@ PostmasterMain(int argc, char *argv[])
 	 * Set up signal handlers for the postmaster process.
 	 *
 	 * CAUTION: when changing this list, check for side-effects on the signal
-	 * handling setup of child processes.  See tcop/postgres.c,
-	 * bootstrap/bootstrap.c, postmaster/bgwriter.c, postmaster/walwriter.c,
-	 * postmaster/autovacuum.c, postmaster/pgarch.c, postmaster/syslogger.c,
-	 * postmaster/bgworker.c and postmaster/checkpointer.c.
+	 * handling setup of child processes.  See
+	 * SetPostmasterChildSignalHandlers()
 	 */
 	pqinitmask();
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
@@ -559,7 +557,6 @@ PostmasterMain(int argc, char *argv[])
 
 	/* This may configure SIGURG, depending on platform. */
 	InitializeWaitEventSupport();
-	InitProcessLocalLatch();
 
 	/*
 	 * No other place in Postgres should touch SIGTTIN/SIGTTOU handling.  We
@@ -580,6 +577,8 @@ PostmasterMain(int argc, char *argv[])
 	pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
 #endif
 
+	/* note: we do not install the standard interrupt handlers in postmaster */
+
 	/* Begin accepting signals. */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -1645,14 +1644,15 @@ ConfigurePostmasterWaitSet(bool accept_connections)
 
 	pm_wait_set = CreateWaitEventSet(NULL,
 									 accept_connections ? (1 + NumListenSockets) : 1);
-	AddWaitEventToSet(pm_wait_set, WL_LATCH_SET, PGINVALID_SOCKET, MyLatch,
+	AddWaitEventToSet(pm_wait_set, WL_INTERRUPT, PGINVALID_SOCKET,
+					  INTERRUPT_CONFIG_RELOAD | INTERRUPT_WAIT_WAKEUP,
 					  NULL);
 
 	if (accept_connections)
 	{
 		for (int i = 0; i < NumListenSockets; i++)
 			AddWaitEventToSet(pm_wait_set, WL_SOCKET_ACCEPT, ListenSockets[i],
-							  NULL, NULL);
+							  0, NULL);
 	}
 }
 
@@ -1681,19 +1681,20 @@ ServerLoop(void)
 								   0 /* postmaster posts no wait_events */ );
 
 		/*
-		 * Latch set by signal handler, or new connection pending on any of
-		 * our sockets? If the latter, fork a child process to deal with it.
+		 * Interrupt raised by signal handler, or new connection pending on
+		 * any of our sockets?  If the latter, fork a child process to deal
+		 * with it.
 		 */
 		for (int i = 0; i < nevents; i++)
 		{
-			if (events[i].events & WL_LATCH_SET)
-				ResetLatch(MyLatch);
+			if (events[i].events & WL_INTERRUPT)
+				ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 			/*
 			 * The following requests are handled unconditionally, even if we
-			 * didn't see WL_LATCH_SET.  This gives high priority to shutdown
-			 * and reload requests where the latch happens to appear later in
-			 * events[] or will be reported by a later call to
+			 * didn't see WL_INTERRUPT.  This gives high priority to shutdown
+			 * and reload requests where the interrupt event happens to appear
+			 * later in events[] or will be reported by a later call to
 			 * WaitEventSetWait().
 			 */
 			if (pending_pm_shutdown_request)
@@ -1985,7 +1986,7 @@ static void
 handle_pm_pmsignal_signal(SIGNAL_ARGS)
 {
 	pending_pm_pmsignal = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1995,7 +1996,7 @@ static void
 handle_pm_reload_request_signal(SIGNAL_ARGS)
 {
 	pending_pm_reload_request = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2072,7 +2073,7 @@ handle_pm_shutdown_request_signal(SIGNAL_ARGS)
 			pending_pm_shutdown_request = true;
 			break;
 	}
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2233,7 +2234,7 @@ static void
 handle_pm_child_exit_signal(SIGNAL_ARGS)
 {
 	pending_pm_child_exit = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -2581,6 +2582,7 @@ CleanupBackend(PMChild *bp,
 	bool		crashed = false;
 	bool		logged = false;
 	pid_t		bp_pid;
+	int			bp_child_slot;
 	bool		bp_bgworker_notify;
 	BackendType bp_bkend_type;
 	RegisteredBgWorker *rw;
@@ -2628,6 +2630,7 @@ CleanupBackend(PMChild *bp,
 	 * detached cleanly.
 	 */
 	bp_pid = bp->pid;
+	bp_child_slot = bp->child_slot;
 	bp_bgworker_notify = bp->bgworker_notify;
 	bp_bkend_type = bp->bkend_type;
 	rw = bp->rw;
@@ -2655,14 +2658,14 @@ CleanupBackend(PMChild *bp,
 	}
 
 	/*
-	 * This backend may have been slated to receive SIGUSR1 when some
-	 * background worker started or stopped.  Cancel those notifications, as
-	 * we don't want to signal PIDs that are not PostgreSQL backends.  This
+	 * This backend may have been slated to receive INTERRUPT_WAIT_WAKEUP when
+	 * some background worker started or stopped.  Cancel those notifications,
+	 * as we don't want to signal PIDs that are not PostgreSQL backends.  This
 	 * gets skipped in the (probably very common) case where the backend has
 	 * never requested any such notifications.
 	 */
 	if (bp_bgworker_notify)
-		BackgroundWorkerStopNotifications(bp_pid);
+		BackgroundWorkerStopNotifications(bp_child_slot);
 
 	/*
 	 * If it was an autovacuum worker, wake up the launcher so that it can
@@ -3921,7 +3924,7 @@ process_pm_pmsignal(void)
  * but we do use in backends.  If we were to SIG_IGN such signals in the
  * postmaster, then a newly started backend might drop a signal that arrives
  * before it's able to reconfigure its signal processing.  (See notes in
- * tcop/postgres.c.)
+ * tcop/postgres.c.) XXX: where are those notes now?
  */
 static void
 dummy_handler(SIGNAL_ARGS)
@@ -4296,15 +4299,15 @@ maybe_start_bgworkers(void)
 		{
 			if (rw->rw_worker.bgw_restart_time == BGW_NEVER_RESTART)
 			{
-				int			notify_pid;
+				ProcNumber	notify_proc_number;
 
-				notify_pid = rw->rw_worker.bgw_notify_pid;
+				notify_proc_number = GetNotifyProcNumberForRegisteredWorker(rw);
 
 				ForgetBackgroundWorker(rw);
 
 				/* Report worker is gone now. */
-				if (notify_pid != 0)
-					kill(notify_pid, SIGUSR1);
+				if (notify_proc_number != INVALID_PROC_NUMBER)
+					SendInterrupt(INTERRUPT_WAIT_WAKEUP, notify_proc_number);
 
 				continue;
 			}
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 330c10ddb0b..bb23642b73f 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -22,12 +22,15 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procsignal.h"
 #include "storage/standby.h"
 #include "utils/guc.h"
@@ -38,21 +41,14 @@
 #ifndef USE_POSTMASTER_DEATH_SIGNAL
 /*
  * On systems that need to make a system call to find out if the postmaster has
- * gone away, we'll do so only every Nth call to ProcessStartupProcInterrupts().
+ * gone away, we'll do so only every Nth call to StartupCheckPostmasterDeath().
  * This only affects how long it takes us to detect the condition while we're
- * busy replaying WAL.  Latch waits and similar which should react immediately
+ * busy replaying WAL.  Interrupt waits and similar should react immediately
  * through the usual techniques.
  */
 #define POSTMASTER_POLL_RATE_LIMIT 1024
 #endif
 
-/*
- * Flags set by interrupt handlers for later service in the redo loop.
- */
-static volatile sig_atomic_t got_SIGHUP = false;
-static volatile sig_atomic_t shutdown_requested = false;
-static volatile sig_atomic_t promote_signaled = false;
-
 /*
  * Flag set when executing a restore command, to tell SIGTERM signal handler
  * that it's safe to just proc_exit.
@@ -77,7 +73,7 @@ int			log_startup_progress_interval = 10000;	/* 10 sec */
 
 /* Signal handlers */
 static void StartupProcTriggerHandler(SIGNAL_ARGS);
-static void StartupProcSigHupHandler(SIGNAL_ARGS);
+static void StartupProcShutdownHandler(SIGNAL_ARGS);
 
 /* Callbacks */
 static void StartupProcExit(int code, Datum arg);
@@ -92,16 +88,12 @@ static void StartupProcExit(int code, Datum arg);
 static void
 StartupProcTriggerHandler(SIGNAL_ARGS)
 {
-	promote_signaled = true;
-	WakeupRecovery();
-}
-
-/* SIGHUP: set flag to re-read config file at next convenient time */
-static void
-StartupProcSigHupHandler(SIGNAL_ARGS)
-{
-	got_SIGHUP = true;
-	WakeupRecovery();
+	/*
+	 * The INTERRUPT_CHECK_PROMOTE flag hints the startup process to check for
+	 * the signal file, while INTERRUPT_WAL_ARRIVED wakes up the process from
+	 * sleep.
+	 */
+	RaiseInterrupt(INTERRUPT_CHECK_PROMOTE | INTERRUPT_WAL_ARRIVED);
 }
 
 /* SIGTERM: set flag to abort redo and exit */
@@ -111,8 +103,16 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
 	if (in_restore_command)
 		proc_exit(1);
 	else
-		shutdown_requested = true;
-	WakeupRecovery();
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+}
+
+/* Process various interrupts that might be sent to the startup process */
+
+static void
+ProcessStartupProcTerminateInterrupt(void)
+{
+	/* we were requested to exit without finishing recovery. */
+	proc_exit(1);
 }
 
 /*
@@ -122,7 +122,8 @@ StartupProcShutdownHandler(SIGNAL_ARGS)
  * to restart it.
  */
 static void
-StartupRereadConfig(void)
+ProcessStartupProcConfigReloadInterrupt(void)
+
 {
 	char	   *conninfo = pstrdup(PrimaryConnInfo);
 	char	   *slotname = pstrdup(PrimarySlotName);
@@ -149,29 +150,13 @@ StartupRereadConfig(void)
 		StartupRequestWalReceiverRestart();
 }
 
-/* Process various signals that might be sent to the startup process */
 void
-ProcessStartupProcInterrupts(void)
+StartupProcCheckPostmasterDeath(void)
 {
 #ifdef POSTMASTER_POLL_RATE_LIMIT
 	static uint32 postmaster_poll_count = 0;
 #endif
 
-	/*
-	 * Process any requests or signals received recently.
-	 */
-	if (got_SIGHUP)
-	{
-		got_SIGHUP = false;
-		StartupRereadConfig();
-	}
-
-	/*
-	 * Check if we were requested to exit without finishing recovery.
-	 */
-	if (shutdown_requested)
-		proc_exit(1);
-
 	/*
 	 * Emergency bailout if postmaster has died.  This is to avoid the
 	 * necessity for manual cleanup of all postmaster children.  Do this less
@@ -184,14 +169,6 @@ ProcessStartupProcInterrupts(void)
 #endif
 		!PostmasterIsAlive())
 		exit(1);
-
-	/* Process barrier events */
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	/* Perform logging of memory contexts of this process */
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
 }
 
 
@@ -225,15 +202,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	/*
 	 * Properly accept or ignore signals the postmaster might send us.
 	 */
-	pqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */
-	pqsignal(SIGINT, SIG_IGN);	/* ignore query cancel */
+
+	/* override SIGTERM because we need a little more complicated handling */
 	pqsignal(SIGTERM, StartupProcShutdownHandler);	/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
+	InitializeTimeouts();		/* establishes SIGALRM handler */
+
 	/*
 	 * Register timeouts needed for standby mode
 	 */
@@ -241,6 +216,13 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	RegisterTimeout(STANDBY_TIMEOUT, StandbyTimeoutHandler);
 	RegisterTimeout(STANDBY_LOCK_TIMEOUT, StandbyLockTimeoutHandler);
 
+	/* Only set up a subset of the standard interrupt handlers */
+	SetStandardInterruptHandlers();
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessStartupProcTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessStartupProcConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+
 	/*
 	 * Unblock signals (they were blocked when the postmaster forked us)
 	 */
@@ -268,7 +250,7 @@ PreRestoreCommand(void)
 	 * shutdown request received just before this.
 	 */
 	in_restore_command = true;
-	if (shutdown_requested)
+	if (InterruptPending(INTERRUPT_TERMINATE))
 		proc_exit(1);
 }
 
@@ -278,18 +260,6 @@ PostRestoreCommand(void)
 	in_restore_command = false;
 }
 
-bool
-IsPromoteSignaled(void)
-{
-	return promote_signaled;
-}
-
-void
-ResetPromoteSignaled(void)
-{
-	promote_signaled = false;
-}
-
 /*
  * Set a flag indicating that it's time to log a progress report.
  */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 8515bfb9653..c9faa76b6a9 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,8 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,13 +41,11 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "tcop/tcopprot.h"
 #include "utils/guc.h"
@@ -272,16 +272,14 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	 * upstream processes are gone, to ensure we don't miss any dying gasps of
 	 * broken backends...
 	 */
-
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
 	pqsignal(SIGINT, SIG_IGN);
 	pqsignal(SIGTERM, SIG_IGN);
 	pqsignal(SIGQUIT, SIG_IGN);
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
+
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
-	pqsignal(SIGUSR2, SIG_IGN);
+
+	SetStandardInterruptHandlers();
+	DisableInterrupt(INTERRUPT_TERMINATE);
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
@@ -322,7 +320,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
@@ -332,9 +330,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 */
@@ -350,14 +351,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);
 
 			/*
@@ -1182,7 +1182,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);
 	}
@@ -1193,8 +1193,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();
@@ -1585,9 +1585,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 4e4eed4d076..d8c754ad280 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -31,17 +31,17 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lwlock.h"
 #include "storage/proc.h"
 #include "storage/procsignal.h"
@@ -146,7 +146,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);
@@ -240,16 +240,15 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 			(errmsg_internal("WAL summarizer started")));
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessWalSummarizerConfigReloadInterrupt);
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
 
 	/* Advertise ourselves. */
 	on_shmem_exit(WalSummarizerShutdown, (Datum) 0);
@@ -309,10 +308,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) */
@@ -349,7 +346,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();
@@ -624,8 +621,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)
@@ -640,7 +637,7 @@ WakeupWalSummarizer(void)
 	LWLockRelease(WALSummarizerLock);
 
 	if (pgprocno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(pgprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, pgprocno);
 }
 
 /*
@@ -849,27 +846,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();
 }
 
 /*
@@ -1008,7 +995,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);
@@ -1495,7 +1482,7 @@ summarizer_read_local_xlog_page(XLogReaderState *state,
 	WALReadError errinfo;
 	SummarizerReadLocalXLogPrivate *private_data;
 
-	ProcessWalSummarizerInterrupts();
+	CHECK_FOR_INTERRUPTS();
 
 	private_data = (SummarizerReadLocalXLogPrivate *)
 		state->private_data;
@@ -1533,7 +1520,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. */
@@ -1633,11 +1620,15 @@ summarizer_wait_for_wal(void)
 	pgstat_report_wal(false);
 
 	/* OK, now sleep. */
-	(void) WaitLatch(MyLatch,
-					 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					 sleep_quanta * MS_PER_SLEEP_QUANTUM,
-					 WAIT_EVENT_WAL_SUMMARIZER_WAL);
-	ResetLatch(MyLatch);
+	(void) WaitInterrupt(INTERRUPT_WAIT_WAKEUP |
+						 INTERRUPT_TERMINATE |
+						 INTERRUPT_CONFIG_RELOAD |
+						 INTERRUPT_BARRIER |
+						 INTERRUPT_LOG_MEMORY_CONTEXT,
+						 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						 sleep_quanta * MS_PER_SLEEP_QUANTUM,
+						 WAIT_EVENT_WAL_SUMMARIZER_WAL);
+	ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 	/* Reset count of pages read. */
 	pages_read_since_last_sleep = 0;
@@ -1684,7 +1675,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,
@@ -1703,7 +1694,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 1851417ad1b..93dfc98cc34 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -45,11 +45,12 @@
 #include <unistd.h>
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
@@ -97,16 +98,14 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	AuxiliaryProcessMainCommon();
 
 	/*
-	 * Properly accept or ignore signals the postmaster might send us
+	 * Set up interrupt handlers
 	 */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, SIG_IGN);	/* no query to cancel */
-	pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN); /* not used */
+	SetStandardInterruptHandlers();
+
+	EnableInterrupt(INTERRUPT_CONFIG_RELOAD);
+	/* we can just proc_exit(0) at any point if requested to terminate */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessAuxProcessShutdownInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
@@ -209,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))
 		{
@@ -222,11 +221,10 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 			SetWalWriterSleeping(hibernating);
 		}
 
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
-		/* Process any signals received recently */
-		ProcessMainLoopInterrupts();
+		/* Process any interrupts received recently */
+		CHECK_FOR_INTERRUPTS();
 
 		/*
 		 * Do what we're here for; then, if XLogBackgroundFlush() found useful
@@ -250,9 +248,9 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 		else
 			cur_timeout = WalWriterDelay * HIBERNATE_FACTOR;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 cur_timeout,
-						 WAIT_EVENT_WAL_WRITER_MAIN);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 cur_timeout,
+							 WAIT_EVENT_WAL_WRITER_MAIN);
 	}
 }
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 7c8639b32e9..156cb976677 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -31,7 +31,6 @@
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 7cd6e912a9c..5af20db657f 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,16 +157,17 @@
 
 #include "postgres.h"
 
+#include "access/parallel.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
 #include "replication/worker_internal.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
@@ -240,12 +241,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -708,30 +703,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)
@@ -761,7 +732,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);
@@ -807,13 +789,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);
 			}
 		}
 		else
@@ -846,9 +832,8 @@ LogicalParallelApplyLoop(shm_mq_handle *mqh)
 static void
 pa_shutdown(int code, Datum arg)
 {
-	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
-				   INVALID_PROC_NUMBER);
+	SendInterrupt(INTERRUPT_PARALLEL_MESSAGE,
+				  MyLogicalRepWorker->leader_pgprocno);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
 }
@@ -873,15 +858,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();
 
 	/*
@@ -942,8 +930,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;
@@ -977,29 +964,7 @@ ParallelApplyWorkerMain(Datum main_arg)
 	set_apply_error_context_origin(originname);
 
 	LogicalParallelApplyLoop(mqh);
-
-	/*
-	 * The parallel apply worker must not get here because the parallel apply
-	 * worker will only stop when it receives a SIGTERM or SIGUSR2 from the
-	 * leader, or SIGINT from itself, or when there is an error. None of these
-	 * cases will allow the code to reach here.
-	 */
-	Assert(false);
-}
-
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	SetLatch(MyLatch);
+	Assert(false);				/* LogicalParallelApplyLoop never returns */
 }
 
 /*
@@ -1066,7 +1031,7 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
  */
 void
 ProcessParallelApplyMessages(void)
@@ -1076,6 +1041,9 @@ ProcessParallelApplyMessages(void)
 
 	static MemoryContext hpam_context = NULL;
 
+	/* We don't expect the leader apply worker to also run parallel queries */
+	Assert(!ParallelContextActive());
+
 	/*
 	 * This is invoked from ProcessInterrupts(), and since some of the
 	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
@@ -1099,8 +1067,6 @@ ProcessParallelApplyMessages(void)
 
 	oldcontext = MemoryContextSwitchTo(hpam_context);
 
-	ParallelApplyMessagePending = false;
-
 	foreach(lc, ParallelApplyWorkerPool)
 	{
 		shm_mq_result res;
@@ -1191,14 +1157,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();
 		}
 
@@ -1263,13 +1230,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 e6112e11ec2..d66ba0b6f5a 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,11 +25,12 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/dshash.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
@@ -58,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;
@@ -183,7 +184,6 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 							   BackgroundWorkerHandle *handle)
 {
 	bool		result = false;
-	bool		dropped_latch = false;
 
 	for (;;)
 	{
@@ -219,27 +219,28 @@ WaitForReplicationWorkerAttach(LogicalRepWorker *worker,
 		}
 
 		/*
-		 * We need timeout because we generally don't get notified via latch
-		 * about the worker attach.  But we don't expect to have to wait long.
+		 * We need timeout because we generally don't get notified via an
+		 * interrupt about the worker attach.  But we don't expect to have to
+		 * wait long.
 		 */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
-			dropped_latch = true;
 		}
 	}
 
 	/*
-	 * If we had to clear a latch event in order to wait, be sure to restore
-	 * it before exiting.  Otherwise caller may miss events.
+	 * XXX: We used to have to restore the process latch, because the launcher
+	 * relied on the same latch to wait for other status changes. But We now
+	 * use INTERRUPT_SUBSCRIPTION_CHANGE for that. But for clariy, perhaps we
+	 * should use a designed interrupt for this wait too?
 	 */
-	if (dropped_latch)
-		SetLatch(MyLatch);
 
 	return result;
 }
@@ -473,6 +474,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
@@ -539,7 +541,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))
@@ -566,7 +567,7 @@ retry:
  * slot.
  */
 static void
-logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
+logicalrep_worker_stop_internal(LogicalRepWorker *worker, InterruptMask interrupt)
 {
 	uint16		generation;
 
@@ -589,13 +590,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_STARTUP);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_STARTUP);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -616,7 +618,7 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 	}
 
 	/* Now terminate the worker ... */
-	kill(worker->proc->pid, signo);
+	SendInterrupt(interrupt, GetNumberFromPGProc(worker->proc));
 
 	/* ... and wait for it to die. */
 	for (;;)
@@ -630,13 +632,14 @@ logicalrep_worker_stop_internal(LogicalRepWorker *worker, int signo)
 		LWLockRelease(LogicalRepWorkerLock);
 
 		/* Wait a bit --- we don't expect to have to wait long. */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_BGWORKER_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -663,7 +666,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);
@@ -672,8 +675,9 @@ logicalrep_worker_stop(LogicalRepWorkerType wtype, Oid subid, Oid relid)
 /*
  * Stop the given logical replication parallel apply worker.
  *
- * Node that the function sends SIGUSR2 instead of SIGTERM to the parallel apply
- * worker so that the worker exits cleanly.
+ * Node that the function sends INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER
+ * instead of INTERRUPT_TERMINATE to the parallel apply worker so that the
+ * worker exits more cleanly. (FIXME: what's the difference really?)
  */
 void
 logicalrep_pa_worker_stop(ParallelApplyWorkerInfo *winfo)
@@ -710,13 +714,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
@@ -738,7 +742,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.
  */
@@ -747,7 +751,7 @@ logicalrep_worker_wakeup_ptr(LogicalRepWorker *worker)
 {
 	Assert(LWLockHeldByMe(LogicalRepWorkerLock));
 
-	SetLatch(&worker->proc->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(worker->proc));
 }
 
 /*
@@ -815,7 +819,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);
@@ -847,6 +851,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;
 }
 
@@ -858,7 +863,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;
 }
 
 /*
@@ -1019,7 +1024,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);
@@ -1045,6 +1049,7 @@ ApplyLauncherShmemInit(void)
 
 		memset(LogicalRepCtx, 0, ApplyLauncherShmemSize());
 
+		LogicalRepCtx->launcher_procno = INVALID_PROC_NUMBER;
 		LogicalRepCtx->last_start_dsa = DSA_HANDLE_INVALID;
 		LogicalRepCtx->last_start_dsh = DSHASH_HANDLE_INVALID;
 
@@ -1193,8 +1198,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);
 }
 
 /*
@@ -1208,13 +1217,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).
@@ -1256,6 +1268,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)
 		{
@@ -1416,21 +1429,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);
 		}
 	}
 
@@ -1585,7 +1596,7 @@ CreateConflictDetectionSlot(void)
 bool
 IsLogicalLauncher(void)
 {
-	return LogicalRepCtx->launcher_pid == MyProcPid;
+	return LogicalRepCtx->launcher_procno == MyProcNumber;
 }
 
 /*
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 9c92fddd624..67750242f7c 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,7 +57,6 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "utils/acl.h"
@@ -494,11 +493,8 @@ copy_sequences(WalReceiverConn *conn)
 
 			CHECK_FOR_INTERRUPTS();
 
-			if (ConfigReloadPending)
-			{
-				ConfigReloadPending = false;
+			if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 				ProcessConfigFile(PGC_SIGHUP);
-			}
 
 			sync_status = get_and_validate_seq_info(slot, &sequence_rel,
 													&seqinfo, &seqidx);
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 5e1ee7e4728..9dbf89cb832 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -59,9 +59,10 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
@@ -79,9 +80,9 @@
 /*
  * Struct for sharing information to control slot synchronization.
  *
- * The 'pid' is either the slot sync worker's pid or the backend's pid running
+ * The 'procno' is either the slot sync worker's procno or the backend's procno running
  * the SQL function pg_sync_replication_slots(). When the startup process sets
- * 'stopSignaled' during promotion, it uses this 'pid' to wake up the currently
+ * 'stopSignaled' during promotion, it uses this 'procno' to wake up the currently
  * synchronizing process so that the process can immediately stop its
  * synchronizing work on seeing 'stopSignaled' set.
  * Setting 'stopSignaled' is also used to handle the race condition when the
@@ -104,7 +105,7 @@
  */
 typedef struct SlotSyncCtxStruct
 {
-	pid_t		pid;
+	ProcNumber	procno;
 	bool		stopSignaled;
 	bool		syncing;
 	time_t		last_start_time;
@@ -1223,7 +1224,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;
@@ -1286,13 +1287,14 @@ slotsync_reread_config(void)
 }
 
 /*
- * Interrupt handler for process performing slot synchronization.
+ * Check for interrupts while performing slot synchronization.
+ *
+ * This does CHECK_FOR_INTERRUPTS(), but also checks for
+ * INTERRUPT_CONFIG_RELOAD and checks for 'stopSignaled'.
  */
 static void
 ProcessSlotSyncInterrupts(void)
 {
-	CHECK_FOR_INTERRUPTS();
-
 	if (SlotSyncCtx->stopSignaled)
 	{
 		if (AmLogicalSlotSyncWorkerProcess())
@@ -1314,7 +1316,9 @@ ProcessSlotSyncInterrupts(void)
 		}
 	}
 
-	if (ConfigReloadPending)
+	CHECK_FOR_INTERRUPTS();
+
+	if (InterruptPending(INTERRUPT_CONFIG_RELOAD))
 		slotsync_reread_config();
 }
 
@@ -1358,7 +1362,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
@@ -1404,13 +1408,16 @@ wait_for_slot_activity(bool some_slot_updated)
 		sleep_ms = MIN_SLOTSYNC_WORKER_NAPTIME_MS;
 	}
 
-	rc = WaitLatch(MyLatch,
-				   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-				   sleep_ms,
-				   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
+	/* Wait for the interrupts that ProcessSlotSyncInterrupts() will handle */
+	rc = WaitInterrupt(CheckForInterruptsMask |
+					   INTERRUPT_WAIT_WAKEUP |
+					   INTERRUPT_CONFIG_RELOAD,
+					   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+					   sleep_ms,
+					   WAIT_EVENT_REPLICATION_SLOTSYNC_MAIN);
 
-	if (rc & WL_LATCH_SET)
-		ResetLatch(MyLatch);
+	if (rc & WL_INTERRUPT)
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1418,7 +1425,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);
 
@@ -1430,16 +1437,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);
 
@@ -1454,7 +1461,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;
@@ -1529,19 +1536,15 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	PG_exception_stack = &local_sigjmp_buf;
 
 	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);
-	pqsignal(SIGTERM, die);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
 
-	check_and_set_sync_info(MyProcPid);
+	SetStandardInterruptHandlers();
+
+	check_and_set_sync_info(MyProcNumber);
 
 	ereport(LOG, errmsg("slot sync worker started"));
 
-	/* Register it as soon as SlotSyncCtx->pid is initialized. */
+	/* Register it as soon as SlotSyncCtx->procno is initialized. */
 	before_shmem_exit(slotsync_worker_onexit, (Datum) 0);
 
 	/*
@@ -1681,7 +1684,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);
 
@@ -1720,7 +1723,7 @@ update_synced_slots_inactive_since(void)
 void
 ShutDownSlotSync(void)
 {
-	pid_t		sync_process_pid;
+	ProcNumber	sync_process_procno;
 
 	SpinLockAcquire(&SlotSyncCtx->mutex);
 
@@ -1737,7 +1740,7 @@ ShutDownSlotSync(void)
 		return;
 	}
 
-	sync_process_pid = SlotSyncCtx->pid;
+	sync_process_procno = SlotSyncCtx->procno;
 
 	SpinLockRelease(&SlotSyncCtx->mutex);
 
@@ -1745,8 +1748,8 @@ ShutDownSlotSync(void)
 	 * Signal process doing slotsync, if any. The process will stop upon
 	 * detecting that the stopSignaled flag is set to true.
 	 */
-	if (sync_process_pid != InvalidPid)
-		kill(sync_process_pid, SIGUSR1);
+	if (sync_process_procno != INVALID_PROC_NUMBER)
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, sync_process_procno);
 
 	/* Wait for slot sync to end */
 	for (;;)
@@ -1754,13 +1757,14 @@ ShutDownSlotSync(void)
 		int			rc;
 
 		/* Wait a bit, we don't expect to have to wait long */
-		rc = WaitLatch(MyLatch,
-					   WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-					   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
+		rc = WaitInterrupt(CheckForInterruptsMask |
+						   INTERRUPT_WAIT_WAKEUP,
+						   WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+						   10L, WAIT_EVENT_REPLICATION_SLOTSYNC_SHUTDOWN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
@@ -1845,7 +1849,7 @@ SlotSyncShmemInit(void)
 	if (!found)
 	{
 		memset(SlotSyncCtx, 0, size);
-		SlotSyncCtx->pid = InvalidPid;
+		SlotSyncCtx->procno = INVALID_PROC_NUMBER;
 		SpinLockInit(&SlotSyncCtx->mutex);
 	}
 }
@@ -1923,7 +1927,7 @@ SyncReplicationSlots(WalReceiverConn *wrconn)
 		List	   *remote_slots = NIL;
 		List	   *slot_names = NIL;	/* List of slot names to track */
 
-		check_and_set_sync_info(MyProcPid);
+		check_and_set_sync_info(MyProcNumber);
 
 		/* Check for interrupts and config changes */
 		ProcessSlotSyncInterrupts();
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index bccbf61bf39..f85bbd67b3b 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,6 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
@@ -112,7 +113,6 @@
 #include "replication/walreceiver.h"
 #include "replication/worker_internal.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
 #include "utils/array.h"
@@ -168,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;
@@ -219,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;
@@ -519,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.
 						 */
@@ -698,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 f9c4b484754..8c31bcad43e 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -261,13 +261,14 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
@@ -281,7 +282,6 @@
 #include "rewrite/rewriteHandler.h"
 #include "storage/buffile.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/lmgr.h"
 #include "storage/procarray.h"
 #include "tcop/tcopprot.h"
@@ -4056,11 +4056,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();
@@ -4179,11 +4176,11 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 			break;
 
 		/*
-		 * Wait for more data or latch.  If we have unflushed transactions,
-		 * wake up after WalWriterDelay to see if they've been flushed yet (in
-		 * which case we should send a feedback message).  Otherwise, there's
-		 * no particular urgency about waking up unless we get data or a
-		 * signal.
+		 * Wait for more data or interrupt.  If we have unflushed
+		 * transactions, wake up after WalWriterDelay to see if they've been
+		 * flushed yet (in which case we should send a feedback message).
+		 * Otherwise, there's no particular urgency about waking up unless we
+		 * get data or a signal.
 		 */
 		if (!dlist_is_empty(&lsn_mapping))
 			wait_time = WalWriterDelay;
@@ -4204,23 +4201,22 @@ LogicalRepApplyLoop(XLogRecPtr last_received)
 				wait_time = Min(wait_time, MySubscription->maxretention);
 		}
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_SOCKET_READABLE | WL_LATCH_SET |
-							   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-							   fd, wait_time,
-							   WAIT_EVENT_LOGICAL_APPLY_MAIN);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+								   INTERRUPT_WAIT_WAKEUP |
+								   INTERRUPT_CONFIG_RELOAD,
+								   WL_SOCKET_READABLE | WL_INTERRUPT |
+								   WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+								   fd, wait_time,
+								   WAIT_EVENT_LOGICAL_APPLY_MAIN);
 
-		if (rc & WL_LATCH_SET)
+		if (rc & WL_INTERRUPT)
 		{
-			ResetLatch(MyLatch);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 			CHECK_FOR_INTERRUPTS();
 		}
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		if (rc & WL_TIMEOUT)
 		{
@@ -5929,8 +5925,13 @@ SetupApplyOrSyncWorker(int worker_slot)
 
 	Assert(am_tablesync_worker() || am_sequencesync_worker() || am_leader_apply_worker());
 
-	/* Setup signal handling */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
+	/* Setup interrupt handling */
+	SetStandardInterruptHandlers();
+
+	/* Override the handler for paralllel messages */
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelApplyMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
 	BackgroundWorkerUnblockSignals();
 
 	/*
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 28c7019402b..9742ddd1fd4 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,9 +44,9 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
@@ -622,7 +622,6 @@ ReplicationSlotAcquire(const char *name, bool nowait, bool error_if_invalid)
 {
 	ReplicationSlot *s;
 	ProcNumber	active_proc;
-	int			active_pid;
 
 	Assert(name != NULL);
 
@@ -685,7 +684,6 @@ retry:
 		s->active_proc = active_proc = MyProcNumber;
 		ReplicationSlotSetInactiveSince(s, 0, true);
 	}
-	active_pid = GetPGProcByNumber(active_proc)->pid;
 	LWLockRelease(ReplicationSlotControlLock);
 
 	/*
@@ -707,7 +705,7 @@ retry:
 		ereport(ERROR,
 				(errcode(ERRCODE_OBJECT_IN_USE),
 				 errmsg("replication slot \"%s\" is active for PID %d",
-						NameStr(s->data.name), active_pid)));
+						NameStr(s->data.name), GetPGProcByNumber(active_proc)->pid)));
 	}
 	else if (!nowait)
 		ConditionVariableCancelSleep(); /* no sleep needed after all */
@@ -1968,7 +1966,7 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 							   Oid dboid, TransactionId snapshotConflictHorizon,
 							   bool *released_lock_out)
 {
-	int			last_signaled_pid = 0;
+	ProcNumber	last_signaled_proc = INVALID_PROC_NUMBER;
 	bool		released_lock = false;
 	bool		invalidated = false;
 	TimestampTz inactive_since = 0;
@@ -1978,7 +1976,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 		XLogRecPtr	restart_lsn;
 		NameData	slotname;
 		ProcNumber	active_proc;
-		int			active_pid = 0;
 		ReplicationSlotInvalidationCause invalidation_cause = RS_INVAL_NONE;
 		TimestampTz now = 0;
 		long		slot_idle_secs = 0;
@@ -2062,11 +2059,6 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			/* Let caller know */
 			invalidated = true;
 		}
-		else
-		{
-			active_pid = GetPGProcByNumber(active_proc)->pid;
-			Assert(active_pid != 0);
-		}
 
 		SpinLockRelease(&s->mutex);
 
@@ -2105,22 +2097,25 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			 * process owns it. To handle that, we signal only if the PID of
 			 * the owning process has changed from the previous time. (This
 			 * logic assumes that the same PID is not reused very quickly.)
+			 *
+			 * FIXME: need another check now that we're using ProcNumbers,
+			 * which do get recycled quickly
 			 */
-			if (last_signaled_pid != active_pid)
+			if (last_signaled_proc != active_proc)
 			{
-				ReportSlotInvalidation(invalidation_cause, true, active_pid,
+				ReportSlotInvalidation(invalidation_cause, true,
+									   GetPGProcByNumber(active_proc)->pid,
 									   slotname, restart_lsn,
 									   oldestLSN, snapshotConflictHorizon,
 									   slot_idle_secs);
 
 				if (MyBackendType == B_STARTUP)
 					(void) SignalRecoveryConflict(GetPGProcByNumber(active_proc),
-												  active_pid,
 												  RECOVERY_CONFLICT_LOGICALSLOT);
 				else
-					(void) kill(active_pid, SIGTERM);
+					SendInterrupt(INTERRUPT_TERMINATE, active_proc);
 
-				last_signaled_pid = active_pid;
+				last_signaled_proc = active_proc;
 			}
 
 			/* Wait until the slot is released. */
@@ -2161,7 +2156,8 @@ InvalidatePossiblyObsoleteSlot(uint32 possible_causes,
 			ReplicationSlotSave();
 			ReplicationSlotRelease();
 
-			ReportSlotInvalidation(invalidation_cause, false, active_pid,
+			ReportSlotInvalidation(invalidation_cause, false,
+								   GetPGProcByNumber(active_proc)->pid,
 								   slotname, restart_lsn,
 								   oldestLSN, snapshotConflictHorizon,
 								   slot_idle_secs);
@@ -3253,11 +3249,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))
@@ -3267,6 +3260,8 @@ WaitForStandbyConfirmation(XLogRecPtr wait_for_lsn)
 		 * Wait for the slots in the synchronized_standby_slots to catch up,
 		 * but use a timeout (1s) so we can also check if the
 		 * synchronized_standby_slots has been changed.
+		 *
+		 * FIXME: I think we need a way to add a CV to WaitEventSet
 		 */
 		ConditionVariableTimedSleep(&WalSndCtl->wal_confirm_rcv_cv, 1000,
 									WAIT_EVENT_WAIT_FOR_STANDBY_CONFIRMATION);
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index d1582a5d711..4ba5ed828bf 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,6 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
@@ -265,22 +266,22 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 	/*
 	 * Wait for specified LSN to be confirmed.
 	 *
-	 * Each proc has its own wait latch, so we perform a normal latch
+	 * Each proc has its own wait interrupt vector, so we perform a normal
 	 * check/wait loop here.
 	 */
 	for (;;)
 	{
 		int			rc;
 
-		/* Must reset the latch before testing state. */
-		ResetLatch(MyLatch);
+		/* Must clear the interrupt flag before testing state. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
-		 * Acquiring the lock is not needed, the latch ensures proper
-		 * barriers. If it looks like we're done, we must really be done,
-		 * because once walsender changes the state to SYNC_REP_WAIT_COMPLETE,
-		 * it will never update it again, so we can't be seeing a stale value
-		 * in that case.
+		 * Acquiring the lock is not needed, the interrupt ensures proper
+		 * barriers (FIXME: is that so?). If it looks like we're done, we must
+		 * really be done, because once walsender changes the state to
+		 * SYNC_REP_WAIT_COMPLETE, it will never update it again, so we can't
+		 * be seeing a stale value in that case.
 		 */
 		if (MyProc->syncRepState == SYNC_REP_WAIT_COMPLETE)
 			break;
@@ -294,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))
 		{
 			ereport(WARNING,
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
@@ -314,9 +315,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.")));
@@ -325,11 +325,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,
@@ -337,7 +341,7 @@ SyncRepWaitForLSN(XLogRecPtr lsn, bool commit)
 		 */
 		if (rc & WL_POSTMASTER_DEATH)
 		{
-			ProcDiePending = true;
+			RaiseInterrupt(INTERRUPT_TERMINATE);
 			whereToSendOutput = DestNone;
 			SyncRepCancelWait();
 			break;
@@ -944,7 +948,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 ec6c516530d..ed53d96c315 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,12 +60,13 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
@@ -246,16 +247,8 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	/* Arrange to clean up at walreceiver exit */
 	on_shmem_exit(WalRcvDie, PointerGetDatum(&startpointTLI));
 
-	/* Properly accept or ignore signals the postmaster might send us */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload); /* set flag to read config
-													 * file */
-	pqsignal(SIGINT, SIG_IGN);
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, SIG_IGN);
+	/* Set up interrupt handlers. We have no special needs. */
+	SetStandardInterruptHandlers();
 
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
@@ -439,9 +432,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();
@@ -514,25 +506,27 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 
 				/*
 				 * Ideally we would reuse a WaitEventSet object repeatedly
-				 * here to avoid the overheads of WaitLatchOrSocket on epoll
-				 * systems, but we can't be sure that libpq (or any other
-				 * walreceiver implementation) has the same socket (even if
-				 * the fd is the same number, it may have been closed and
+				 * here to avoid the overheads of WaitInterruptOrSocket on
+				 * epoll systems, but we can't be sure that libpq (or any
+				 * other walreceiver implementation) has the same socket (even
+				 * if the fd is the same number, it may have been closed and
 				 * reopened since the last time).  In future, if there is a
 				 * function for removing sockets from WaitEventSet, then we
 				 * could add and remove just the socket each time, potentially
 				 * avoiding some system calls.
 				 */
 				Assert(wait_fd != PGINVALID_SOCKET);
-				rc = WaitLatchOrSocket(MyLatch,
-									   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
-									   WL_TIMEOUT | WL_LATCH_SET,
-									   wait_fd,
-									   nap,
-									   WAIT_EVENT_WAL_RECEIVER_MAIN);
-				if (rc & WL_LATCH_SET)
+				rc = WaitInterruptOrSocket(CheckForInterruptsMask |
+										   INTERRUPT_WAIT_WAKEUP |
+										   INTERRUPT_CONFIG_RELOAD,
+										   WL_EXIT_ON_PM_DEATH | WL_SOCKET_READABLE |
+										   WL_TIMEOUT | WL_INTERRUPT,
+										   wait_fd,
+										   nap,
+										   WAIT_EVENT_WAL_RECEIVER_MAIN);
+				if (rc & WL_INTERRUPT)
 				{
-					ResetLatch(MyLatch);
+					ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 					CHECK_FOR_INTERRUPTS();
 
 					if (walrcv->force_reply)
@@ -680,7 +674,7 @@ WalRcvWaitForStartPosition(XLogRecPtr *startpoint, TimeLineID *startpointTLI)
 	WakeupRecovery();
 	for (;;)
 	{
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		CHECK_FOR_INTERRUPTS();
 
@@ -712,8 +706,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)
@@ -1384,7 +1382,7 @@ WalRcvForceReply(void)
 	procno = WalRcv->procno;
 	SpinLockRelease(&WalRcv->mutex);
 	if (procno != INVALID_PROC_NUMBER)
-		SetLatch(&GetPGProcByNumber(procno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, procno);
 }
 
 /*
diff --git a/src/backend/replication/walreceiverfuncs.c b/src/backend/replication/walreceiverfuncs.c
index e62e8a20420..933c4e41557 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"
@@ -332,7 +333,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 5acc8ee3ac8..446eafe694f 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -25,14 +25,14 @@
  * the walsender will exit quickly without sending any more XLOG records.
  *
  * If the server is shut down, checkpointer sends us
- * PROCSIG_WALSND_INIT_STOPPING after all regular backends have exited.  If
+ * INTERRUPT_WALSND_INIT_STOPPING after all regular backends have exited.  If
  * the backend is idle or runs an SQL query this causes the backend to
  * shutdown, if logical replication is in progress all existing WAL records
  * are processed followed by a shutdown.  Otherwise this causes the walsender
  * to switch to the "stopping" state. In this state, the walsender will reject
  * any further replication commands. The checkpointer begins the shutdown
  * checkpoint once all walsenders are confirmed as stopping. When the shutdown
- * checkpoint finishes, the postmaster sends us SIGUSR2. This instructs
+ * checkpoint finishes, the postmaster sends us INTERRUPT_WALSND_STOP. This instructs
  * walsender to send any outstanding WAL, including the shutdown checkpoint
  * record, wait for it to be replicated to the standby, and then exit.
  *
@@ -63,13 +63,14 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
@@ -201,15 +202,12 @@ static bool streamingDoneReceiving;
 /* Are we there yet? */
 static bool WalSndCaughtUp = false;
 
-/* Flags set by signal handlers for later service in main loop */
-static volatile sig_atomic_t got_SIGUSR2 = false;
-static volatile sig_atomic_t got_STOPPING = false;
-
 /*
  * This is set while we are streaming. When not set
- * PROCSIG_WALSND_INIT_STOPPING signal will be handled like SIGTERM. When set,
- * the main loop is responsible for checking got_STOPPING and terminating when
- * it's set (after streaming any remaining WAL).
+ * INTERRUPT_WALSND_INIT_STOPPING interrupt will be handled like SIGTERM.
+ * When set, the main loop is responsible for checking
+ * INTERRUPT_WALSND_INIT_STOPPING and terminating when it's set (after
+ * streaming any remaining WAL).
  */
 static volatile sig_atomic_t replication_active = false;
 
@@ -282,7 +280,7 @@ static void WalSndKeepalive(bool requestReply, XLogRecPtr writePtr);
 static void WalSndKeepaliveIfNecessary(void);
 static void WalSndCheckTimeOut(void);
 static long WalSndComputeSleeptime(TimestampTz now);
-static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event);
+static void WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask);
 static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write);
 static void WalSndUpdateProgress(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid,
@@ -370,7 +368,8 @@ WalSndErrorCleanup(void)
 	if (!IsTransactionOrTransactionBlock())
 		ReleaseAuxProcessResources(false);
 
-	if (got_STOPPING || got_SIGUSR2)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ||
+		InterruptPending(INTERRUPT_WALSND_STOP))
 		proc_exit(0);
 
 	/* Revert back to startup state */
@@ -738,8 +737,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();
@@ -773,7 +780,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)
@@ -972,12 +981,14 @@ StartReplication(StartReplicationCmd *cmd)
 		SyncRepInitConfig();
 
 		/* Main loop of walsender */
+		DisableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 		replication_active = true;
 
 		WalSndLoop(XLogSendPhysical);
 
 		replication_active = false;
-		if (got_STOPPING)
+		EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			proc_exit(0);
 		WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1036,9 +1047,9 @@ StartReplication(StartReplicationCmd *cmd)
  * XLogReaderRoutine->page_read callback for logical decoding contexts, as a
  * walsender process.
  *
- * Inside the walsender we can do better than read_local_xlog_page,
- * which has to do a plain sleep/busy loop, because the walsender's latch gets
- * set every time WAL is flushed.
+ * Inside the walsender we can do better than read_local_xlog_page, which has
+ * to do a plain sleep/busy loop, because the walsender's interrupt gets set
+ * every time WAL is flushed.
  */
 static int
 logical_read_xlog_page(XLogReaderState *state, XLogRecPtr targetPagePtr, int reqLen,
@@ -1475,7 +1486,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	{
 		ereport(LOG,
 				(errmsg("terminating walsender process after promotion")));
-		got_STOPPING = true;
+		RaiseInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
 	}
 
 	/*
@@ -1529,7 +1540,7 @@ StartLogicalReplication(StartReplicationCmd *cmd)
 	ReplicationSlotRelease();
 
 	replication_active = false;
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		proc_exit(0);
 	WalSndSetState(WALSNDSTATE_STARTUP);
 
@@ -1619,12 +1630,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();
 
@@ -1664,23 +1671,27 @@ ProcessPendingWrites(void)
 
 		/* Sleep until something happens or we time out */
 		WalSndWait(WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE, sleeptime,
-				   WAIT_EVENT_WAL_SENDER_WRITE_DATA);
+				   WAIT_EVENT_WAL_SENDER_WRITE_DATA,
+				   CheckForInterruptsMask |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 
 		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
-
-		CHECK_FOR_INTERRUPTS();
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/* Process any requests or signals received recently */
-		WalSndHandleConfigReload();
+		CHECK_FOR_INTERRUPTS();
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
+			ProcessWalSndConfigReloadInterrupt();
 
 		/* Try to flush pending output to the client */
 		if (pq_flush_if_writable() != 0)
 			WalSndShutdown();
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt so WalSndLoop knows to continue */
+	/* FIXME: still needed? */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -1783,7 +1794,7 @@ PhysicalWakeupLogicalWalSnd(void)
 static bool
 NeedToWaitForStandbys(XLogRecPtr flushed_lsn, uint32 *wait_event)
 {
-	int			elevel = got_STOPPING ? ERROR : WARNING;
+	int			elevel = InterruptPending(INTERRUPT_WALSND_INIT_STOPPING) ? ERROR : WARNING;
 	bool		failover_slot;
 
 	failover_slot = (replication_active && MyReplicationSlot->data.failover);
@@ -1870,12 +1881,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();
@@ -1885,7 +1896,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * otherwise we'd possibly end up waiting for WAL that never gets
 		 * written, because walwriter has shut down already.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 			XLogBackgroundFlush();
 
 		/*
@@ -1910,7 +1921,7 @@ WalSndWaitForWal(XLogRecPtr loc)
 		 * RecentFlushPtr, so we can send all remaining data before shutting
 		 * down.
 		 */
-		if (got_STOPPING)
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		{
 			if (NeedToWaitForStandbys(RecentFlushPtr, &wait_event))
 				wait_for_standby_at_stop = true;
@@ -1992,11 +2003,15 @@ WalSndWaitForWal(XLogRecPtr loc)
 			last_flush = now;
 		}
 
-		WalSndWait(wakeEvents, sleeptime, wait_event);
+		WalSndWait(wakeEvents, sleeptime, wait_event,
+				   CheckForInterruptsMask |
+				   (wait_for_standby_at_stop ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+				   INTERRUPT_CONFIG_RELOAD |
+				   INTERRUPT_WAIT_WAKEUP);
 	}
 
-	/* reactivate latch so WalSndLoop knows to continue */
-	SetLatch(MyLatch);
+	/* reactivate interrupt flag so WalSndLoop knows to continue */
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 	return RecentFlushPtr;
 }
 
@@ -2022,7 +2037,7 @@ exec_replication_command(const char *cmd_string)
 	 * If WAL sender has been told that shutdown is getting close, switch its
 	 * status accordingly to handle the next replication commands correctly.
 	 */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	/*
@@ -2895,6 +2910,7 @@ static void
 WalSndLoop(WalSndSendDataCallback send_data)
 {
 	TimestampTz last_flush = 0;
+	bool		stopping = false;
 
 	/*
 	 * Initialize the last reply timestamp. That enables timeout processing
@@ -2909,13 +2925,20 @@ WalSndLoop(WalSndSendDataCallback send_data)
 	 */
 	for (;;)
 	{
-		/* Clear any already-pending wakeups */
-		ResetLatch(MyLatch);
+		/*
+		 * Remember if we received INTERRUPT_WALSND_INIT_STOPPING before the
+		 * processing below already.
+		 */
+		if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+			stopping = true;
 
-		CHECK_FOR_INTERRUPTS();
+		/* Clear any already-pending wakeups */
+		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();
@@ -2964,13 +2987,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);
 		}
 
@@ -3022,7 +3045,12 @@ WalSndLoop(WalSndSendDataCallback send_data)
 			}
 
 			/* Sleep until something happens or we time out */
-			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN);
+			WalSndWait(wakeEvents, sleeptime, WAIT_EVENT_WAL_SENDER_MAIN,
+					   CheckForInterruptsMask |
+					   (stopping ? 0 : INTERRUPT_WALSND_INIT_STOPPING) |
+					   INTERRUPT_CONFIG_RELOAD |
+					   INTERRUPT_WAIT_WAKEUP
+				);
 		}
 	}
 }
@@ -3060,6 +3088,7 @@ InitWalSenderSlot(void)
 			/*
 			 * Found a free slot. Reserve it for us.
 			 */
+			walsnd->pgprocno = MyProcNumber;
 			walsnd->pid = MyProcPid;
 			walsnd->state = WALSNDSTATE_STARTUP;
 			walsnd->sentPtr = InvalidXLogRecPtr;
@@ -3116,6 +3145,7 @@ WalSndKill(int code, Datum arg)
 	SpinLockAcquire(&walsnd->mutex);
 	/* Mark WalSnd struct as no longer being in use. */
 	walsnd->pid = 0;
+	walsnd->pgprocno = 0;
 	SpinLockRelease(&walsnd->mutex);
 }
 
@@ -3210,7 +3240,7 @@ XLogSendPhysical(void)
 	Size		rbytes;
 
 	/* If requested switch the WAL sender to the stopping state. */
-	if (got_STOPPING)
+	if (InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
 		WalSndSetState(WALSNDSTATE_STOPPING);
 
 	if (streamingDoneSending)
@@ -3579,8 +3609,8 @@ XLogSendLogical(void)
 	 * terminate the connection in an orderly manner, after writing out all
 	 * the pending data.
 	 */
-	if (WalSndCaughtUp && got_STOPPING)
-		got_SIGUSR2 = true;
+	if (WalSndCaughtUp && InterruptPending(INTERRUPT_WALSND_INIT_STOPPING))
+		RaiseInterrupt(INTERRUPT_WALSND_STOP);
 
 	/* Update shared memory status */
 	{
@@ -3697,52 +3727,43 @@ WalSndRqstFileReload(void)
 	}
 }
 
-/*
- * Handle PROCSIG_WALSND_INIT_STOPPING signal.
- */
-void
-HandleWalSndInitStopping(void)
-{
-	Assert(am_walsender);
-
-	/*
-	 * If replication has not yet started, die like with SIGTERM. If
-	 * replication is active, only set a flag and wake up the main loop. It
-	 * will send any outstanding WAL, wait for it to be replicated to the
-	 * standby, and then exit gracefully.
-	 */
-	if (!replication_active)
-		kill(MyProcPid, SIGTERM);
-	else
-		got_STOPPING = true;
-}
-
 /*
  * SIGUSR2: set flag to do a last cycle and shut down afterwards. The WAL
  * sender should already have been switched to WALSNDSTATE_STOPPING at
  * this point.
+ *
+ * XXX: This is still needed because the postmaster cannot send
+ * INTERRUPT_WALSND_STOP directly.
  */
 static void
 WalSndLastCycleHandler(SIGNAL_ARGS)
 {
-	got_SIGUSR2 = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_WALSND_STOP);
 }
 
 /* Set up signal handlers */
 void
 WalSndSignals(void)
 {
-	/* Set up signal handlers */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, StatementCancelHandler);	/* query cancel */
-	pqsignal(SIGTERM, die);		/* request shutdown */
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
+	/* Set up interrupt handlers */
 	InitializeTimeouts();		/* establishes SIGALRM handler */
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
-												 * shutdown */
+
+	/*
+	 * If replication has not yet started, die like with SIGTERM. When
+	 * replication starts, we disable this handler and check the flag
+	 * explicitly. The main loop will send any outstanding WAL, wait for it to
+	 * be replicated to the standby, and then exit gracefully.
+	 */
+	SetInterruptHandler(INTERRUPT_WALSND_INIT_STOPPING, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_WALSND_INIT_STOPPING);
+
+	/*
+	 * On INTERRUPT_WALSND_STOP is used to flush remaining WAL (the shutdown
+	 * checkpoint record) and exit. We check for that explicitly, so no
+	 * handler function. Postmaster cannot send the interrupt directly, so we
+	 * set up a Unix signal handler to raise it.
+	 */
+	pqsignal(SIGUSR2, WalSndLastCycleHandler);
 }
 
 /* Report shared-memory space needed by WalSndShmemInit */
@@ -3825,24 +3846,28 @@ WalSndWakeup(bool physical, bool logical)
  * on postmaster death.
  */
 static void
-WalSndWait(uint32 socket_events, long timeout, uint32 wait_event)
+WalSndWait(uint32 socket_events, long timeout, uint32 wait_event, uint32 interruptMask)
 {
 	WaitEvent	event;
 
-	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, NULL);
+	/* for condition variables */
+	interruptMask |= INTERRUPT_WAIT_WAKEUP;
+
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetSocketPos, socket_events, 0);
+	ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetInterruptPos, WL_INTERRUPT, interruptMask);
 
 	/*
 	 * We use a condition variable to efficiently wake up walsenders in
 	 * WalSndWakeup().
 	 *
 	 * Every walsender prepares to sleep on a shared memory CV. Note that it
-	 * just prepares to sleep on the CV (i.e., adds itself to the CV's
-	 * waitlist), but does not actually wait on the CV (IOW, it never calls
-	 * ConditionVariableSleep()). It still uses WaitEventSetWait() for
-	 * waiting, because we also need to wait for socket events. The processes
-	 * (startup process, walreceiver etc.) wanting to wake up walsenders use
-	 * ConditionVariableBroadcast(), which in turn calls SetLatch(), helping
-	 * walsenders come out of WaitEventSetWait().
+	 * just prepares to sleep on the CV waitlist), but does not actually wait
+	 * on the CV (IOW, it never calls ConditionVariableSleep()). It still uses
+	 * WaitEventSetWait() for waiting, because we also need to wait for socket
+	 * events. The processes (startup process, walreceiver etc.) wanting to
+	 * wake up walsenders use ConditionVariableBroadcast(), which in turn
+	 * calls SendInterrupt(), helping walsenders come out of
+	 * WaitEventSetWait().
 	 *
 	 * This approach is simple and efficient because, one doesn't have to loop
 	 * through all the walsenders slots, with a spinlock acquisition and
@@ -3890,16 +3915,16 @@ WalSndInitStopping(void)
 	for (i = 0; i < max_wal_senders; i++)
 	{
 		WalSnd	   *walsnd = &WalSndCtl->walsnds[i];
-		pid_t		pid;
+		ProcNumber	procno;
 
 		SpinLockAcquire(&walsnd->mutex);
-		pid = walsnd->pid;
+		procno = walsnd->pgprocno;
 		SpinLockRelease(&walsnd->mutex);
 
-		if (pid == 0)
+		if (procno == INVALID_PROC_NUMBER)
 			continue;
 
-		SendProcSignal(pid, PROCSIG_WALSND_INIT_STOPPING, INVALID_PROC_NUMBER);
+		SendInterrupt(INTERRUPT_WALSND_INIT_STOPPING, procno);
 	}
 }
 
diff --git a/src/backend/storage/aio/aio.c b/src/backend/storage/aio/aio.c
index e4ae3031fef..da2601e8b92 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -390,7 +390,7 @@ pgaio_io_update_state(PgAioHandle *ioh, PgAioHandleState new_state)
 	 * interrupt processing could wait for the IO to complete, while in an
 	 * intermediary state.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	pgaio_debug_io(DEBUG5, ioh,
 				   "updating state to %s",
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..78789b3f8f3 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -164,7 +164,7 @@ pgaio_io_before_start(PgAioHandle *ioh)
 	 * Otherwise the FDs referenced by the IO could be closed due to interrupt
 	 * processing.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 }
 
 /*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d9617c20e76..4c21b0f48a3 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -13,7 +13,7 @@
  *
  * So that the submitter can make just one system call when submitting a batch
  * of IOs, wakeups "fan out"; each woken IO worker can wake two more. XXX This
- * could be improved by using futexes instead of latches to wake N waiters.
+ * could be improved by using futexes instead of interrupts to wake N waiters.
  *
  * This method of AIO is available in all builds on all operating systems, and
  * is the default.
@@ -29,17 +29,16 @@
 
 #include "postgres.h"
 
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
 #include "storage/io_worker.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "tcop/tcopprot.h"
 #include "utils/injection_point.h"
@@ -62,7 +61,7 @@ typedef struct PgAioWorkerSubmissionQueue
 
 typedef struct PgAioWorkerSlot
 {
-	Latch	   *latch;
+	ProcNumber	procno;
 	bool		in_use;
 } PgAioWorkerSlot;
 
@@ -154,7 +153,7 @@ pgaio_worker_shmem_init(bool first_time)
 		io_worker_control->idle_worker_mask = 0;
 		for (int i = 0; i < MAX_IO_WORKERS; ++i)
 		{
-			io_worker_control->workers[i].latch = NULL;
+			io_worker_control->workers[i].procno = INVALID_PROC_NUMBER;
 			io_worker_control->workers[i].in_use = false;
 		}
 	}
@@ -244,7 +243,7 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 {
 	PgAioHandle *synchronous_ios[PGAIO_SUBMIT_BATCH_SIZE];
 	int			nsync = 0;
-	Latch	   *wakeup = NULL;
+	ProcNumber	wakeup = INVALID_PROC_NUMBER;
 	int			worker;
 
 	Assert(num_staged_ios <= PGAIO_SUBMIT_BATCH_SIZE);
@@ -263,12 +262,12 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 			continue;
 		}
 
-		if (wakeup == NULL)
+		if (wakeup == INVALID_PROC_NUMBER)
 		{
 			/* Choose an idle worker to wake up if we haven't already. */
 			worker = pgaio_worker_choose_idle();
 			if (worker >= 0)
-				wakeup = io_worker_control->workers[worker].latch;
+				wakeup = io_worker_control->workers[worker].procno;
 
 			pgaio_debug_io(DEBUG4, staged_ios[i],
 						   "choosing worker %d",
@@ -277,8 +276,10 @@ pgaio_worker_submit_internal(int num_staged_ios, PgAioHandle **staged_ios)
 	}
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
-	if (wakeup)
-		SetLatch(wakeup);
+	if (wakeup != INVALID_PROC_NUMBER)
+	{
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, wakeup);
+	}
 
 	/* Run whatever is left synchronously. */
 	if (nsync > 0)
@@ -314,11 +315,11 @@ pgaio_worker_die(int code, Datum arg)
 {
 	LWLockAcquire(AioWorkerSubmissionQueueLock, LW_EXCLUSIVE);
 	Assert(io_worker_control->workers[MyIoWorkerId].in_use);
-	Assert(io_worker_control->workers[MyIoWorkerId].latch == MyLatch);
+	Assert(io_worker_control->workers[MyIoWorkerId].procno == MyProcNumber);
 
 	io_worker_control->idle_worker_mask &= ~(UINT64_C(1) << MyIoWorkerId);
 	io_worker_control->workers[MyIoWorkerId].in_use = false;
-	io_worker_control->workers[MyIoWorkerId].latch = NULL;
+	io_worker_control->workers[MyIoWorkerId].procno = INVALID_PROC_NUMBER;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 }
 
@@ -341,20 +342,20 @@ pgaio_worker_register(void)
 	{
 		if (!io_worker_control->workers[i].in_use)
 		{
-			Assert(io_worker_control->workers[i].latch == NULL);
+			Assert(io_worker_control->workers[i].procno == INVALID_PROC_NUMBER);
 			io_worker_control->workers[i].in_use = true;
 			MyIoWorkerId = i;
 			break;
 		}
 		else
-			Assert(io_worker_control->workers[i].latch != NULL);
+			Assert(io_worker_control->workers[i].procno != INVALID_PROC_NUMBER);
 	}
 
 	if (MyIoWorkerId == -1)
 		elog(ERROR, "couldn't find a free worker slot");
 
 	io_worker_control->idle_worker_mask |= (UINT64_C(1) << MyIoWorkerId);
-	io_worker_control->workers[MyIoWorkerId].latch = MyLatch;
+	io_worker_control->workers[MyIoWorkerId].procno = MyProcNumber;
 	LWLockRelease(AioWorkerSubmissionQueueLock);
 
 	on_shmem_exit(pgaio_worker_die, 0);
@@ -392,20 +393,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	AuxiliaryProcessMainCommon();
 
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGINT, die);		/* to allow manually triggering worker restart */
+	/* xxx: this used 'die'. Any reason? */
+	pqsignal(SIGINT, SignalHandlerForShutdownRequest);	/* to allow manually
+														 * triggering worker
+														 * restart */
 
 	/*
 	 * Ignore SIGTERM, will get explicit shutdown via SIGUSR2 later in the
 	 * shutdown sequence, similar to checkpointer.
 	 */
 	pqsignal(SIGTERM, SIG_IGN);
-	/* SIGQUIT handler was already set up by InitPostmasterChild */
-	pqsignal(SIGALRM, SIG_IGN);
-	pqsignal(SIGPIPE, SIG_IGN);
-	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
+	SetStandardInterruptHandlers();
+
 	/* also registers a shutdown callback to unregister */
 	pgaio_worker_register();
 
@@ -453,11 +454,11 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
-	while (!ShutdownRequestPending)
+	while (!InterruptPending(INTERRUPT_TERMINATE))
 	{
 		uint32		io_index;
-		Latch	   *latches[IO_WORKER_WAKEUP_FANOUT];
-		int			nlatches = 0;
+		ProcNumber	procnos[IO_WORKER_WAKEUP_FANOUT];
+		int			nprocnos = 0;
 		int			nwakeups = 0;
 		int			worker;
 
@@ -490,13 +491,13 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 			{
 				if ((worker = pgaio_worker_choose_idle()) < 0)
 					break;
-				latches[nlatches++] = io_worker_control->workers[worker].latch;
+				procnos[nprocnos++] = io_worker_control->workers[worker].procno;
 			}
 		}
 		LWLockRelease(AioWorkerSubmissionQueueLock);
 
-		for (int i = 0; i < nlatches; ++i)
-			SetLatch(latches[i]);
+		for (int i = 0; i < nprocnos; ++i)
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, procnos[i]);
 
 		if (io_index != -1)
 		{
@@ -567,18 +568,20 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
 		}
 		else
 		{
-			WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, -1,
-					  WAIT_EVENT_IO_WORKER_MAIN);
-			ResetLatch(MyLatch);
+			WaitInterrupt(CheckForInterruptsMask |
+						  INTERRUPT_CONFIG_RELOAD |
+						  INTERRUPT_TERMINATE |
+						  INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH,
+						  -1,
+						  WAIT_EVENT_IO_WORKER_MAIN);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 		}
 
 		CHECK_FOR_INTERRUPTS();
 
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 	}
 
 	error_context_stack = errcallback.previous;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index d1babaff023..cb61e6e1fce 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -46,6 +46,7 @@
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "miscadmin.h"
 #include "pg_trace.h"
@@ -3345,7 +3346,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);
@@ -3525,7 +3526,7 @@ BufferSync(int flags)
 						0);
 
 		/* Check for barrier events in case NBuffers is large. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3606,7 +3607,7 @@ BufferSync(int flags)
 		s->num_to_scan++;
 
 		/* Check for barrier events. */
-		if (ProcSignalBarrierPending)
+		if (ConsumeInterrupt(INTERRUPT_BARRIER))
 			ProcessProcSignalBarrier();
 	}
 
@@ -3697,7 +3698,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);
 	}
@@ -6643,12 +6644,18 @@ LockBufferForCleanup(Buffer buffer)
 			SetStartupBufferPinWaitBufId(-1);
 		}
 		else
-			ProcWaitForSignal(WAIT_EVENT_BUFFER_CLEANUP);
+		{
+			WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+						  WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+						  WAIT_EVENT_BUFFER_CLEANUP);
+			ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
+			CHECK_FOR_INTERRUPTS();
+		}
 
 		/*
 		 * Remove flag marking us as waiter. Normally this will not be set
-		 * anymore, but ProcWaitForSignal() can return for other signals as
-		 * well.  We take care to only reset the flag if we're the waiter, as
+		 * anymore, but WaitInterrupt can return for other signals as well. We
+		 * take care to only reset the flag if we're the waiter, as
 		 * theoretically another backend could have started waiting. That's
 		 * impossible with the current usages due to table level locking, but
 		 * better be safe.
diff --git a/src/backend/storage/buffer/freelist.c b/src/backend/storage/buffer/freelist.c
index b7687836188..c3e9759b9b0 100644
--- a/src/backend/storage/buffer/freelist.c
+++ b/src/backend/storage/buffer/freelist.c
@@ -15,6 +15,7 @@
  */
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "storage/buf_internals.h"
@@ -196,27 +197,25 @@ StrategyGetBuffer(BufferAccessStrategy strategy, uint64 *buf_state, bool *from_r
 	/*
 	 * If asked, we need to waken the bgwriter. Since we don't want to rely on
 	 * a spinlock for this we force a read from shared memory once, and then
-	 * set the latch based on that value. We need to go through that length
-	 * because otherwise bgwprocno might be reset while/after we check because
-	 * the compiler might just reread from memory.
+	 * send the interrupt based on that value. We need to go through that
+	 * length because otherwise bgwprocno might be reset while/after we check
+	 * because the compiler might just reread from memory.
 	 *
-	 * This can possibly set the latch of the wrong process if the bgwriter
-	 * dies in the wrong moment. But since PGPROC->procLatch is never
-	 * deallocated the worst consequence of that is that we set the latch of
-	 * some arbitrary process.
+	 * This can possibly send the interrupt to the wrong process if the
+	 * bgwriter dies in the wrong moment, but that's harmless.
 	 */
 	bgwprocno = INT_ACCESS_ONCE(StrategyControl->bgwprocno);
 	if (bgwprocno != -1)
 	{
-		/* reset bgwprocno first, before setting the latch */
+		/* reset bgwprocno first, before sending the interrupt */
 		StrategyControl->bgwprocno = -1;
 
 		/*
-		 * Not acquiring ProcArrayLock here which is slightly icky. It's
-		 * actually fine because procLatch isn't ever freed, so we just can
-		 * potentially set the wrong process' (or no process') latch.
+		 * Not acquiring ProcArrayLock here which is slightly icky, because we
+		 * can potentially send the interrupt to the wrong process (or no
+		 * process), but it's harmless.
 		 */
-		SetLatch(&GetPGProcByNumber(bgwprocno)->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, bgwprocno);
 	}
 
 	/*
@@ -347,10 +346,10 @@ StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc)
 }
 
 /*
- * StrategyNotifyBgWriter -- set or clear allocation notification latch
+ * StrategyNotifyBgWriter -- set or clear allocation notification process
  *
  * If bgwprocno isn't -1, the next invocation of StrategyGetBuffer will
- * set that latch.  Pass -1 to clear the pending notification before it
+ * interrupt that process.  Pass -1 to clear the pending notification before it
  * happens.  This feature is used by the bgwriter process to wake itself up
  * from hibernation, and is not meant for anybody else to use.
  */
diff --git a/src/backend/storage/ipc/Makefile b/src/backend/storage/ipc/Makefile
index 9a07f6e1d92..2d98a43e749 100644
--- a/src/backend/storage/ipc/Makefile
+++ b/src/backend/storage/ipc/Makefile
@@ -15,7 +15,6 @@ OBJS = \
 	dsm_registry.o \
 	ipc.o \
 	ipci.o \
-	latch.o \
 	pmsignal.o \
 	procarray.o \
 	procsignal.o \
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c
index e208457df27..367c1168216 100644
--- a/src/backend/storage/ipc/dsm_impl.c
+++ b/src/backend/storage/ipc/dsm_impl.c
@@ -357,8 +357,8 @@ dsm_impl_posix_resize(int fd, off_t size)
 	/*
 	 * Block all blockable signals, except SIGQUIT.  posix_fallocate() can run
 	 * for quite a long time, and is an all-or-nothing operation.  If we
-	 * allowed SIGUSR1 to interrupt us repeatedly (for example, due to
-	 * recovery conflicts), the retry loop might never succeed.
+	 * allowed signals to interrupt us repeatedly (for example, due to config
+	 * file reloads), the retry loop might never succeed.
 	 */
 	if (IsUnderPostmaster)
 		sigprocmask(SIG_SETMASK, &BlockSig, &save_sigmask);
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..806947571ae 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
@@ -172,13 +173,12 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests; we're doing our best to
-	 * close up shop already.  Note that the signal handlers will not set
-	 * these flags again, now that proc_exit_inprogress is set.
+	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
+	 * to prevent any more interrupts from being processed; we're doing our
+	 * best to close up shop already.
 	 */
-	InterruptPending = false;
-	ProcDiePending = false;
-	QueryCancelPending = false;
+	ClearInterrupt(INTERRUPT_TERMINATE);
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 	InterruptHoldoffCount = 1;
 	CritSectionCount = 0;
 
diff --git a/src/backend/storage/ipc/latch.c b/src/backend/storage/ipc/latch.c
deleted file mode 100644
index 8537e9fef2d..00000000000
--- a/src/backend/storage/ipc/latch.c
+++ /dev/null
@@ -1,389 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * latch.c
- *	  Routines for inter-process latches
- *
- * The latch interface is a reliable replacement for the common pattern of
- * using pg_usleep() or select() to wait until a signal arrives, where the
- * signal handler sets a flag variable.  See latch.h for more information
- * on how to use them.
- *
- * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * IDENTIFICATION
- *	  src/backend/storage/ipc/latch.c
- *
- *-------------------------------------------------------------------------
- */
-#include "postgres.h"
-
-#include "miscadmin.h"
-#include "port/atomics.h"
-#include "storage/latch.h"
-#include "storage/waiteventset.h"
-#include "utils/resowner.h"
-
-/* A common WaitEventSet used to implement WaitLatch() */
-static WaitEventSet *LatchWaitSet;
-
-/* The positions of the latch and PM death events in LatchWaitSet */
-#define LatchWaitSetLatchPos 0
-#define LatchWaitSetPostmasterDeathPos 1
-
-void
-InitializeLatchWaitSet(void)
-{
-	int			latch_pos PG_USED_FOR_ASSERTS_ONLY;
-
-	Assert(LatchWaitSet == NULL);
-
-	/* Set up the WaitEventSet used by WaitLatch(). */
-	LatchWaitSet = CreateWaitEventSet(NULL, 2);
-	latch_pos = AddWaitEventToSet(LatchWaitSet, WL_LATCH_SET, PGINVALID_SOCKET,
-								  MyLatch, NULL);
-	Assert(latch_pos == LatchWaitSetLatchPos);
-
-	/*
-	 * WaitLatch will modify this to WL_EXIT_ON_PM_DEATH or
-	 * WL_POSTMASTER_DEATH on each call.
-	 */
-	if (IsUnderPostmaster)
-	{
-		latch_pos = AddWaitEventToSet(LatchWaitSet, WL_EXIT_ON_PM_DEATH,
-									  PGINVALID_SOCKET, NULL, NULL);
-		Assert(latch_pos == LatchWaitSetPostmasterDeathPos);
-	}
-}
-
-/*
- * Initialize a process-local latch.
- */
-void
-InitLatch(Latch *latch)
-{
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = MyProcPid;
-	latch->is_shared = false;
-
-#ifdef WIN32
-	latch->event = CreateEvent(NULL, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif							/* WIN32 */
-}
-
-/*
- * Initialize a shared latch that can be set from other processes. The latch
- * is initially owned by no-one; use OwnLatch to associate it with the
- * current process.
- *
- * InitSharedLatch needs to be called in postmaster before forking child
- * processes, usually right after allocating the shared memory block
- * containing the latch with ShmemInitStruct. (The Unix implementation
- * doesn't actually require that, but the Windows one does.) Because of
- * this restriction, we have no concurrency issues to worry about here.
- *
- * Note that other handles created in this module are never marked as
- * inheritable.  Thus we do not need to worry about cleaning up child
- * process references to postmaster-private latches or WaitEventSets.
- */
-void
-InitSharedLatch(Latch *latch)
-{
-#ifdef WIN32
-	SECURITY_ATTRIBUTES sa;
-
-	/*
-	 * Set up security attributes to specify that the events are inherited.
-	 */
-	ZeroMemory(&sa, sizeof(sa));
-	sa.nLength = sizeof(sa);
-	sa.bInheritHandle = TRUE;
-
-	latch->event = CreateEvent(&sa, TRUE, FALSE, NULL);
-	if (latch->event == NULL)
-		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
-#endif
-
-	latch->is_set = false;
-	latch->maybe_sleeping = false;
-	latch->owner_pid = 0;
-	latch->is_shared = true;
-}
-
-/*
- * Associate a shared latch with the current process, allowing it to
- * wait on the latch.
- *
- * Although there is a sanity check for latch-already-owned, we don't do
- * any sort of locking here, meaning that we could fail to detect the error
- * if two processes try to own the same latch at about the same time.  If
- * there is any risk of that, caller must provide an interlock to prevent it.
- */
-void
-OwnLatch(Latch *latch)
-{
-	int			owner_pid;
-
-	/* Sanity checks */
-	Assert(latch->is_shared);
-
-	owner_pid = latch->owner_pid;
-	if (owner_pid != 0)
-		elog(PANIC, "latch already owned by PID %d", owner_pid);
-
-	latch->owner_pid = MyProcPid;
-}
-
-/*
- * Disown a shared latch currently owned by the current process.
- */
-void
-DisownLatch(Latch *latch)
-{
-	Assert(latch->is_shared);
-	Assert(latch->owner_pid == MyProcPid);
-
-	latch->owner_pid = 0;
-}
-
-/*
- * Wait for a given latch to be set, or for postmaster death, or until timeout
- * is exceeded. 'wakeEvents' is a bitmask that specifies which of those events
- * to wait for. If the latch is already set (and WL_LATCH_SET is given), the
- * function returns immediately.
- *
- * The "timeout" is given in milliseconds. It must be >= 0 if WL_TIMEOUT flag
- * is given.  Although it is declared as "long", we don't actually support
- * timeouts longer than INT_MAX milliseconds.  Note that some extra overhead
- * is incurred when WL_TIMEOUT is given, so avoid using a timeout if possible.
- *
- * The latch must be owned by the current process, ie. it must be a
- * process-local latch initialized with InitLatch, or a shared latch
- * associated with the current process by calling OwnLatch.
- *
- * Returns bit mask indicating which condition(s) caused the wake-up. Note
- * that if multiple wake-up conditions are true, there is no guarantee that
- * we return all of them in one call, but we will return at least one.
- */
-int
-WaitLatch(Latch *latch, int wakeEvents, long timeout,
-		  uint32 wait_event_info)
-{
-	WaitEvent	event;
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	/*
-	 * Some callers may have a latch other than MyLatch, or no latch at all,
-	 * or want to handle postmaster death differently.  It's cheap to assign
-	 * those, so just do it every time.
-	 */
-	if (!(wakeEvents & WL_LATCH_SET))
-		latch = NULL;
-	ModifyWaitEvent(LatchWaitSet, LatchWaitSetLatchPos, WL_LATCH_SET, latch);
-
-	if (IsUnderPostmaster)
-		ModifyWaitEvent(LatchWaitSet, LatchWaitSetPostmasterDeathPos,
-						(wakeEvents & (WL_EXIT_ON_PM_DEATH | WL_POSTMASTER_DEATH)),
-						NULL);
-
-	if (WaitEventSetWait(LatchWaitSet,
-						 (wakeEvents & WL_TIMEOUT) ? timeout : -1,
-						 &event, 1,
-						 wait_event_info) == 0)
-		return WL_TIMEOUT;
-	else
-		return event.events;
-}
-
-/*
- * Like WaitLatch, but with an extra socket argument for WL_SOCKET_*
- * conditions.
- *
- * When waiting on a socket, EOF and error conditions always cause the socket
- * to be reported as readable/writable/connected, so that the caller can deal
- * with the condition.
- *
- * wakeEvents must include either WL_EXIT_ON_PM_DEATH for automatic exit
- * if the postmaster dies or WL_POSTMASTER_DEATH for a flag set in the
- * return value if the postmaster dies.  The latter is useful for rare cases
- * where some behavior other than immediate exit is needed.
- *
- * NB: These days this is just a wrapper around the WaitEventSet API. When
- * using a latch very frequently, consider creating a longer living
- * WaitEventSet instead; that's more efficient.
- */
-int
-WaitLatchOrSocket(Latch *latch, int wakeEvents, pgsocket sock,
-				  long timeout, uint32 wait_event_info)
-{
-	int			ret = 0;
-	int			rc;
-	WaitEvent	event;
-	WaitEventSet *set = CreateWaitEventSet(CurrentResourceOwner, 3);
-
-	if (wakeEvents & WL_TIMEOUT)
-		Assert(timeout >= 0);
-	else
-		timeout = -1;
-
-	if (wakeEvents & WL_LATCH_SET)
-		AddWaitEventToSet(set, WL_LATCH_SET, PGINVALID_SOCKET,
-						  latch, NULL);
-
-	/* Postmaster-managed callers must handle postmaster death somehow. */
-	Assert(!IsUnderPostmaster ||
-		   (wakeEvents & WL_EXIT_ON_PM_DEATH) ||
-		   (wakeEvents & WL_POSTMASTER_DEATH));
-
-	if ((wakeEvents & WL_POSTMASTER_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_POSTMASTER_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if ((wakeEvents & WL_EXIT_ON_PM_DEATH) && IsUnderPostmaster)
-		AddWaitEventToSet(set, WL_EXIT_ON_PM_DEATH, PGINVALID_SOCKET,
-						  NULL, NULL);
-
-	if (wakeEvents & WL_SOCKET_MASK)
-	{
-		int			ev;
-
-		ev = wakeEvents & WL_SOCKET_MASK;
-		AddWaitEventToSet(set, ev, sock, NULL, NULL);
-	}
-
-	rc = WaitEventSetWait(set, timeout, &event, 1, wait_event_info);
-
-	if (rc == 0)
-		ret |= WL_TIMEOUT;
-	else
-	{
-		ret |= event.events & (WL_LATCH_SET |
-							   WL_POSTMASTER_DEATH |
-							   WL_SOCKET_MASK);
-	}
-
-	FreeWaitEventSet(set);
-
-	return ret;
-}
-
-/*
- * Sets a latch and wakes up anyone waiting on it.
- *
- * This is cheap if the latch is already set, otherwise not so much.
- *
- * NB: when calling this in a signal handler, be sure to save and restore
- * errno around it.  (That's standard practice in most signal handlers, of
- * course, but we used to omit it in handlers that only set a flag.)
- *
- * NB: this function is called from critical sections and signal handlers so
- * throwing an error is not a good idea.
- */
-void
-SetLatch(Latch *latch)
-{
-#ifndef WIN32
-	pid_t		owner_pid;
-#else
-	HANDLE		handle;
-#endif
-
-	/*
-	 * The memory barrier has to be placed here to ensure that any flag
-	 * variables possibly changed by this process have been flushed to main
-	 * memory, before we check/set is_set.
-	 */
-	pg_memory_barrier();
-
-	/* Quick exit if already set */
-	if (latch->is_set)
-		return;
-
-	latch->is_set = true;
-
-	pg_memory_barrier();
-	if (!latch->maybe_sleeping)
-		return;
-
-#ifndef WIN32
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler. We use the self-pipe or SIGURG to ourselves
-	 * to wake up WaitEventSetWaitBlock() without races in that case. If it's
-	 * another process, send a signal.
-	 *
-	 * Fetch owner_pid only once, in case the latch is concurrently getting
-	 * owned or disowned. XXX: This assumes that pid_t is atomic, which isn't
-	 * guaranteed to be true! In practice, the effective range of pid_t fits
-	 * in a 32 bit integer, and so should be atomic. In the worst case, we
-	 * might end up signaling the wrong process. Even then, you're very
-	 * unlucky if a process with that bogus pid exists and belongs to
-	 * Postgres; and PG database processes should handle excess SIGUSR1
-	 * interrupts without a problem anyhow.
-	 *
-	 * Another sort of race condition that's possible here is for a new
-	 * process to own the latch immediately after we look, so we don't signal
-	 * it. This is okay so long as all callers of ResetLatch/WaitLatch follow
-	 * the standard coding convention of waiting at the bottom of their loops,
-	 * not the top, so that they'll correctly process latch-setting events
-	 * that happen before they enter the loop.
-	 */
-	owner_pid = latch->owner_pid;
-	if (owner_pid == 0)
-		return;
-	else if (owner_pid == MyProcPid)
-		WakeupMyProc();
-	else
-		WakeupOtherProc(owner_pid);
-
-#else
-
-	/*
-	 * See if anyone's waiting for the latch. It can be the current process if
-	 * we're in a signal handler.
-	 *
-	 * Use a local variable here just in case somebody changes the event field
-	 * concurrently (which really should not happen).
-	 */
-	handle = latch->event;
-	if (handle)
-	{
-		SetEvent(handle);
-
-		/*
-		 * Note that we silently ignore any errors. We might be in a signal
-		 * handler or other critical path where it's not safe to call elog().
-		 */
-	}
-#endif
-}
-
-/*
- * Clear the latch. Calling WaitLatch after this will sleep, unless
- * the latch is set again before the WaitLatch call.
- */
-void
-ResetLatch(Latch *latch)
-{
-	/* Only the owner should reset the latch */
-	Assert(latch->owner_pid == MyProcPid);
-	Assert(latch->maybe_sleeping == false);
-
-	latch->is_set = false;
-
-	/*
-	 * Ensure that the write to is_set gets flushed to main memory before we
-	 * examine any flag variables.  Otherwise a concurrent SetLatch might
-	 * falsely conclude that it needn't signal us, even though we have missed
-	 * seeing some flag updates that SetLatch was supposed to inform us of.
-	 */
-	pg_memory_barrier();
-}
diff --git a/src/backend/storage/ipc/meson.build b/src/backend/storage/ipc/meson.build
index 9c1ca954d9d..14745c04e13 100644
--- a/src/backend/storage/ipc/meson.build
+++ b/src/backend/storage/ipc/meson.build
@@ -7,7 +7,6 @@ backend_sources += files(
   'dsm_registry.c',
   'ipc.c',
   'ipci.c',
-  'latch.c',
   'pmsignal.c',
   'procarray.c',
   'procsignal.c',
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 40312df2cac..b34e2cb731e 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"
@@ -3450,27 +3451,27 @@ GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid)
  *
  * The 'pid' is redundant with 'proc', but it acts as a cross-check to
  * detect process had exited and the PGPROC entry was reused for a different
- * process.
+ * process. FIXME: lost the crosscheck. Re-introduce a session ID or something?
  *
  * Returns true if the process was signaled, or false if not found.
  */
 bool
-SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason)
+SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason)
 {
 	bool		found = false;
 
 	LWLockAcquire(ProcArrayLock, LW_SHARED);
 
 	/*
-	 * Kill the pid if it's still here. If not, that's what we wanted so
+	 * Kill the process if it's still here. If not, that's what we wanted so
 	 * ignore any errors.
 	 */
-	if (proc->pid == pid)
+	if (proc->pid != 0)
 	{
 		(void) pg_atomic_fetch_or_u32(&proc->pendingRecoveryConflicts, (1 << reason));
 
 		/* wake up the process */
-		(void) SendProcSignal(pid, PROCSIG_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
+		SendInterrupt(INTERRUPT_RECOVERY_CONFLICT, GetNumberFromPGProc(proc));
 		found = true;
 	}
 
@@ -3510,10 +3511,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;
 		}
@@ -3545,21 +3546,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 d47d180a32f..235bf5dc401 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -19,6 +19,7 @@
 
 #include "access/parallel.h"
 #include "commands/async.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -27,8 +28,8 @@
 #include "replication/walsender.h"
 #include "storage/condition_variable.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
+#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinval.h"
 #include "storage/smgr.h"
@@ -36,28 +37,21 @@
 #include "utils/memutils.h"
 
 /*
- * The SIGUSR1 signal is multiplexed to support signaling multiple event
- * types. The specific reason is communicated via flags in shared memory.
- * We keep a boolean flag for each possible "reason", so that different
- * reasons can be signaled to a process concurrently.  (However, if the same
- * reason is signaled more than once nearly simultaneously, the process may
- * observe it only once.)
+ * State for the ProcSignalBarrier mechanism and query cancellation.
  *
- * Each process that wants to receive signals registers its process ID
- * in the ProcSignalSlots array. The array is indexed by ProcNumber to make
- * slot allocation simple, and to avoid having to search the array when you
- * know the ProcNumber of the process you're signaling.  (We do support
- * signaling without ProcNumber, but it's a bit less efficient.)
+ * Each process that wants to participate in barriers or query cancellation
+ * registers its process ID in the ProcSignalSlots array. The array is indexed
+ * by ProcNumber to make slot allocation simple.
  *
  * The fields in each slot are protected by a spinlock, pss_mutex. pss_pid can
  * also be read without holding the spinlock, as a quick preliminary check
  * when searching for a particular PID in the array.
  *
- * pss_signalFlags are intended to be set in cases where we don't need to
- * keep track of whether or not the target process has handled the signal,
- * but sometimes we need confirmation, as when making a global state change
- * that cannot be considered complete until all backends have taken notice
- * of it. For such use cases, we set a bit in pss_barrierCheckMask and then
+ * Plain interrupts are intended to be set in cases where we don't need to
+ * keep track of whether or not the target process has handled the signal, but
+ * sometimes we need confirmation, as when making a global state change that
+ * cannot be considered complete until all backends have taken notice of
+ * it. For such use cases, we set a bit in pss_barrierCheckMask and then
  * increment the current "barrier generation"; when the new barrier generation
  * (or greater) appears in the pss_barrierGeneration flag of every process,
  * we know that the message has been received everywhere.
@@ -67,7 +61,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) */
@@ -107,7 +100,6 @@ struct ProcSignalHeader
 NON_EXEC_STATIC ProcSignalHeader *ProcSignal = NULL;
 static ProcSignalSlot *MyProcSignalSlot = NULL;
 
-static bool CheckProcSignal(ProcSignalReason reason);
 static void CleanupProcSignalState(int status, Datum arg);
 static void ResetProcSignalBarrierBits(uint32 flags);
 
@@ -152,7 +144,6 @@ ProcSignalShmemInit(void)
 			SpinLockInit(&slot->pss_mutex);
 			pg_atomic_init_u32(&slot->pss_pid, 0);
 			slot->pss_cancel_key_len = 0;
-			MemSet(slot->pss_signalFlags, 0, sizeof(slot->pss_signalFlags));
 			pg_atomic_init_u64(&slot->pss_barrierGeneration, PG_UINT64_MAX);
 			pg_atomic_init_u32(&slot->pss_barrierCheckMask, 0);
 			ConditionVariableInit(&slot->pss_barrierCV);
@@ -183,9 +174,6 @@ ProcSignalInit(const uint8 *cancel_key, int cancel_key_len)
 	/* Value used for sanity check below */
 	old_pss_pid = pg_atomic_read_u32(&slot->pss_pid);
 
-	/* Clear out any leftover signal reasons */
-	MemSet(slot->pss_signalFlags, 0, NUM_PROCSIGNALS * sizeof(sig_atomic_t));
-
 	/*
 	 * Initialize barrier state. Since we're a brand-new process, there
 	 * shouldn't be any leftover backend-private state that needs to be
@@ -234,9 +222,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;
@@ -271,73 +260,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
@@ -382,8 +304,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
@@ -394,25 +316,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;
@@ -472,23 +381,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.
  *
@@ -504,12 +396,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
@@ -641,68 +529,7 @@ static void
 ResetProcSignalBarrierBits(uint32 flags)
 {
 	pg_atomic_fetch_or_u32(&MyProcSignalSlot->pss_barrierCheckMask, flags);
-	ProcSignalBarrierPending = true;
-	InterruptPending = true;
-}
-
-/*
- * CheckProcSignal - check to see if a particular reason has been
- * signaled, and clear the signal flag.  Should be called after receiving
- * SIGUSR1.
- */
-static bool
-CheckProcSignal(ProcSignalReason reason)
-{
-	volatile ProcSignalSlot *slot = MyProcSignalSlot;
-
-	if (slot != NULL)
-	{
-		/*
-		 * Careful here --- don't clear flag if we haven't seen it set.
-		 * pss_signalFlags is of type "volatile sig_atomic_t" to allow us to
-		 * read it here safely, without holding the spinlock.
-		 */
-		if (slot->pss_signalFlags[reason])
-		{
-			slot->pss_signalFlags[reason] = false;
-			return true;
-		}
-	}
-
-	return false;
-}
-
-/*
- * procsignal_sigusr1_handler - handle SIGUSR1 signal.
- */
-void
-procsignal_sigusr1_handler(SIGNAL_ARGS)
-{
-	if (CheckProcSignal(PROCSIG_CATCHUP_INTERRUPT))
-		HandleCatchupInterrupt();
-
-	if (CheckProcSignal(PROCSIG_NOTIFY_INTERRUPT))
-		HandleNotifyInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_MESSAGE))
-		HandleParallelMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_WALSND_INIT_STOPPING))
-		HandleWalSndInitStopping();
-
-	if (CheckProcSignal(PROCSIG_BARRIER))
-		HandleProcSignalBarrierInterrupt();
-
-	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
-		HandleLogMemoryContextInterrupt();
-
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_RECOVERY_CONFLICT))
-		HandleRecoveryConflictInterrupt();
-
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_BARRIER);
 }
 
 /*
@@ -761,6 +588,10 @@ SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len)
 				/*
 				 * If we have setsid(), signal the backend's whole process
 				 * group
+				 *
+				 * FIXME: could we send INTERRUPT_QUERY_CANCEL here directly?
+				 * We don't have a PGPROC entry yet, but the target backend
+				 * presumably does.
 				 */
 #ifdef HAVE_SETSID
 				kill(-backendPID, SIGINT);
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 7e9fbc00705..d00e4af45eb 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -4,7 +4,7 @@
  *	  single-reader, single-writer shared memory message queue
  *
  * Both the sender and the receiver must have a PGPROC; their respective
- * process latches are used for synchronization.  Only the sender may send,
+ * process interrupts are used for synchronization.  Only the sender may send,
  * and only the receiver may receive.  This is intended to allow a user
  * backend to communicate with worker backends that it has registered.
  *
@@ -18,6 +18,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
@@ -45,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.
@@ -113,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
@@ -215,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));
 }
 
 /*
@@ -233,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));
 }
 
 /*
@@ -342,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
@@ -540,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;
 	}
 
@@ -558,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)
@@ -620,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)
 	{
@@ -896,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));
 }
 
 /*
@@ -994,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
@@ -1002,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;
@@ -1010,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();
@@ -1055,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.
 			 */
@@ -1151,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);
 	}
 }
 
@@ -1251,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;
@@ -1294,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 d48b4fe3799..aaeebca6c91 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,6 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
@@ -41,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
@@ -201,12 +205,12 @@ pg_wait_until_termination(int pid, int64 timeout)
 		/* Process interrupts, if any, before waiting */
 		CHECK_FOR_INTERRUPTS();
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 waittime,
-						 WAIT_EVENT_BACKEND_TERMINATION);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 waittime,
+							 WAIT_EVENT_BACKEND_TERMINATION);
 
-		ResetLatch(MyLatch);
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		remainingtime -= waittime;
 	} while (remainingtime > 0);
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index 5559f7c1cfa..dcabd25956a 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,30 +15,13 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
-
 uint64		SharedInvalidMessageCounter;
 
-
-/*
- * Because backends sitting idle will not be reading sinval events, we
- * need a way to give an idle backend a swift kick in the rear and make
- * it catch up before the sinval queue overflows and forces it to go
- * through a cache reset exercise.  This is done by sending
- * PROCSIG_CATCHUP_INTERRUPT to any backend that gets too far behind.
- *
- * The signal handler will set an interrupt pending flag and will set the
- * processes latch. Whenever starting to read from the client, or when
- * interrupted while doing so, ProcessClientReadInterrupt() will call
- * ProcessCatchupEvent().
- */
-volatile sig_atomic_t catchupInterruptPending = false;
-
-
 /*
  * SendSharedInvalidMessages
  *	Add shared-cache-invalidation message(s) to the global SI message queue.
@@ -132,38 +115,13 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 	 * catchup signal this way avoids creating spikes in system load for what
 	 * should be just a background maintenance activity.
 	 */
-	if (catchupInterruptPending)
+	if (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP))
 	{
-		catchupInterruptPending = false;
 		elog(DEBUG4, "sinval catchup complete, cleaning queue");
 		SICleanupQueue(false, 0);
 	}
 }
 
-
-/*
- * HandleCatchupInterrupt
- *
- * This is called when PROCSIG_CATCHUP_INTERRUPT is received.
- *
- * We used to directly call ProcessCatchupEvent directly when idle. These days
- * we just set a flag to do it later and notify the process of that fact by
- * setting the process's latch.
- */
-void
-HandleCatchupInterrupt(void)
-{
-	/*
-	 * Note: this is called by a SIGNAL HANDLER. You must be very wary what
-	 * you do here.
-	 */
-
-	catchupInterruptPending = true;
-
-	/* make sure the event is processed in due course */
-	SetLatch(MyLatch);
-}
-
 /*
  * ProcessCatchupInterrupt
  *
@@ -173,15 +131,15 @@ HandleCatchupInterrupt(void)
 void
 ProcessCatchupInterrupt(void)
 {
-	while (catchupInterruptPending)
+	do
 	{
 		/*
 		 * What we need to do here is cause ReceiveSharedInvalidMessages() to
-		 * run, which will do the necessary work and also reset the
-		 * catchupInterruptPending flag.  If we are inside a transaction we
-		 * can just call AcceptInvalidationMessages() to do this.  If we
-		 * aren't, we start and immediately end a transaction; the call to
-		 * AcceptInvalidationMessages() happens down inside transaction start.
+		 * run, which will do the necessary work.  If we are inside a
+		 * transaction we can just call AcceptInvalidationMessages() to do
+		 * this.  If we aren't, we start and immediately end a transaction;
+		 * the call to AcceptInvalidationMessages() happens down inside
+		 * transaction start.
 		 *
 		 * It is awfully tempting to just call AcceptInvalidationMessages()
 		 * without the rest of the xact start/stop overhead, and I think that
@@ -190,14 +148,20 @@ ProcessCatchupInterrupt(void)
 		 */
 		if (IsTransactionOrTransactionBlock())
 		{
-			elog(DEBUG4, "ProcessCatchupEvent inside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt inside transaction");
 			AcceptInvalidationMessages();
 		}
 		else
 		{
-			elog(DEBUG4, "ProcessCatchupEvent outside transaction");
+			elog(DEBUG4, "ProcessCatchupInterrupt outside transaction");
 			StartTransactionCommand();
 			CommitTransactionCommand();
 		}
+
+		/*
+		 * If another catchup interrupt arrived while we were procesing the
+		 * previous one, process the new one too.
+		 */
 	}
+	while (ConsumeInterrupt(INTERRUPT_SINVAL_CATCHUP));
 }
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index a7a7cc4f0a9..42c987dcb91 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -17,11 +17,11 @@
 #include <signal.h>
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
 #include "storage/procnumber.h"
-#include "storage/procsignal.h"
 #include "storage/shmem.h"
 #include "storage/sinvaladt.h"
 #include "storage/spin.h"
@@ -118,7 +118,7 @@
  * we exceed CLEANUP_MIN.  Should be a power of 2 for speed.
  *
  * SIG_THRESHOLD: the minimum number of messages a backend must have fallen
- * behind before we'll send it PROCSIG_CATCHUP_INTERRUPT.
+ * behind before we'll send it INTERRUPT_SINVAL_CATCHUP.
  *
  * WRITE_QUANTUM: the max number of messages to push into the buffer per
  * iteration of SIInsertDataEntries.  Noncritical but should be less than
@@ -314,6 +314,14 @@ SharedInvalBackendInit(bool sendOnly)
 
 	/* register exit routine to mark my entry inactive at exit */
 	on_shmem_exit(CleanupInvalidationState, PointerGetDatum(segP));
+
+	/*
+	 * all processes that participate in shared invalidations must handle
+	 * catchup interrupts promptly. (Client backends will disable this while
+	 * processing a query, but enable by default.)
+	 */
+	SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt);
+	EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
 }
 
 /*
@@ -565,7 +573,7 @@ SIGetDataEntries(SharedInvalidationMessage *data, int datasize)
  * minFree is the minimum number of message slots to make free.
  *
  * Possible side effects of this routine include marking one or more
- * backends as "reset" in the array, and sending PROCSIG_CATCHUP_INTERRUPT
+ * backends as "reset" in the array, and sending INTERRUPT_SINVAL_CATCHUP
  * to some backend that seems to be getting too far behind.  We signal at
  * most one backend at a time, for reasons explained at the top of the file.
  *
@@ -658,8 +666,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		segP->nextThreshold = (numMsgs / CLEANUP_QUANTUM + 1) * CLEANUP_QUANTUM;
 
 	/*
-	 * Lastly, signal anyone who needs a catchup interrupt.  Since
-	 * SendProcSignal() might not be fast, we don't want to hold locks while
+	 * Lastly, signal anyone who needs a catchup interrupt.  SendInterrupt()
+	 * is pretty fast, but we nevertheless don't want to hold locks while
 	 * executing it.
 	 */
 	if (needSig)
@@ -670,8 +678,8 @@ SICleanupQueue(bool callerHasWriteLock, int minFree)
 		needSig->signaled = true;
 		LWLockRelease(SInvalReadLock);
 		LWLockRelease(SInvalWriteLock);
-		elog(DEBUG4, "sending sinval catchup signal to PID %d", (int) his_pid);
-		SendProcSignal(his_pid, PROCSIG_CATCHUP_INTERRUPT, his_procNumber);
+		elog(DEBUG4, "sending sinval catchup interrupt to backend PID %d", (int) his_pid);
+		SendInterrupt(INTERRUPT_SINVAL_CATCHUP, his_procNumber);
 		if (callerHasWriteLock)
 			LWLockAcquire(SInvalWriteLock, LW_EXCLUSIVE);
 	}
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index d83afbfb9d6..c9242d6fb89 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,6 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/slot.h"
@@ -596,7 +597,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
@@ -698,7 +699,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
@@ -746,7 +751,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:
@@ -766,15 +775,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
@@ -838,9 +848,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);
@@ -936,6 +956,7 @@ void
 StandbyDeadLockHandler(void)
 {
 	got_standby_deadlock_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -945,6 +966,7 @@ void
 StandbyTimeoutHandler(void)
 {
 	got_standby_delay_timeout = true;
+	RaiseInterrupt(INTERRUPT_WAIT_WAKEUP);
 }
 
 /*
@@ -954,6 +976,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 772e350a0c0..612de8b35e9 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -8,8 +8,8 @@
  * pselect() (as opposed to plain poll()/select()).
  *
  * You can wait for:
- * - a latch being set from another process or from signal handler in the same
- *   process (WL_LATCH_SET)
+ * - an interrupt from another process or from signal handler in the same
+ *   process (WL_INTERRUPT)
  * - data to become readable or writeable on a socket (WL_SOCKET_*)
  * - postmaster death (WL_POSTMASTER_DEATH or WL_EXIT_ON_PM_DEATH)
  * - timeout (WL_TIMEOUT)
@@ -17,15 +17,16 @@
  * Implementation
  * --------------
  *
- * The poll() implementation uses the so-called self-pipe trick to overcome the
- * race condition involved with poll() and setting a global flag in the signal
- * handler. When a latch is set and the current process is waiting for it, the
- * signal handler wakes up the poll() in WaitLatch by writing a byte to a pipe.
- * A signal by itself doesn't interrupt poll() on all platforms, and even on
- * platforms where it does, a signal that arrives just before the poll() call
- * does not prevent poll() from entering sleep. An incoming byte on a pipe
- * however reliably interrupts the sleep, and causes poll() to return
- * immediately even if the signal arrives before poll() begins.
+ * The poll() implementation uses the so-called self-pipe trick to overcome
+ * the race condition involved with poll() and setting a global flag in the
+ * signal handler. When an interrupt is received and the current process is
+ * waiting waiting for it, the signal handler wakes up the poll() in
+ * WaitInterrupt by writing a byte to a pipe.  A signal by itself doesn't
+ * interrupt poll() on all platforms, and even on platforms where it does, a
+ * signal that arrives just before the poll() call does not prevent poll()
+ * from entering sleep. An incoming byte on a pipe however reliably interrupts
+ * the sleep, and causes poll() to return immediately even if the signal
+ * arrives before poll() begins.
  *
  * The epoll() implementation overcomes the race with a different technique: it
  * keeps SIGURG blocked and consumes from a signalfd() descriptor instead.  We
@@ -73,7 +74,7 @@
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/pmsignal.h"
-#include "storage/latch.h"
+#include "storage/proc.h"
 #include "storage/waiteventset.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
@@ -128,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,7 +168,7 @@ struct WaitEventSet
 };
 
 #ifndef WIN32
-/* Are we currently in WaitLatch? The signal handler would like to know. */
+/* Are we currently on a WaitEventSet? The signal handler would like to know. */
 static volatile sig_atomic_t waiting = false;
 #endif
 
@@ -183,9 +184,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
 
@@ -235,7 +243,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)
@@ -285,12 +293,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");
@@ -311,7 +319,7 @@ InitializeWaitEventSupport(void)
 	ReserveExternalFD();
 	ReserveExternalFD();
 
-	pqsignal(SIGURG, latch_sigurg_handler);
+	pqsignal(SIGURG, interrupt_sigurg_handler);
 #endif
 
 #ifdef WAIT_USE_SIGNALFD
@@ -349,6 +357,12 @@ InitializeWaitEventSupport(void)
 	/* Ignore SIGURG, because we'll receive it via kqueue. */
 	pqsignal(SIGURG, SIG_IGN);
 #endif
+
+#ifdef WAIT_USE_WIN32
+	LocalInterruptWakeupEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+	if (LocalInterruptWakeupEvent == NULL)
+		elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 }
 
 /*
@@ -412,7 +426,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;
 
@@ -496,9 +511,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)
 		{
@@ -535,7 +550,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
@@ -553,10 +568,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.
@@ -566,7 +577,7 @@ FreeWaitEventSetAfterFork(WaitEventSet *set)
  * events.
  */
 int
-AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, Latch *latch,
+AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd, InterruptMask interruptMask,
 				  void *user_data)
 {
 	WaitEvent  *event;
@@ -580,19 +591,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 */
@@ -608,10 +616,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)
@@ -645,14 +653,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)
@@ -682,40 +690,34 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, Latch *latch)
 	}
 
 	/*
-	 * If neither the event mask nor the associated latch changes, return
-	 * early. That's an important optimization for some sockets, where
+	 * If neither the event mask nor the associated interrupt mask changes,
+	 * return early. That's an important optimization for some sockets, where
 	 * ModifyWaitEvent is frequently used to switch from waiting for reads to
 	 * waiting on writes.
 	 */
 	if (events == event->events &&
-		(!(event->events & WL_LATCH_SET) || set->latch == latch))
+		(!(event->events & WL_INTERRUPT) || set->interrupt_mask == interruptMask))
 		return;
 
-	if (event->events & WL_LATCH_SET && events != event->events)
-		elog(ERROR, "cannot modify latch event");
+	if (event->events & WL_INTERRUPT && events != event->events)
+		elog(ERROR, "cannot modify interrupts event");
 
 	/* FIXME: validate event mask */
 	event->events = events;
 
-	if (events == WL_LATCH_SET)
+	if (events == WL_INTERRUPT)
 	{
-		if (latch && latch->owner_pid != MyProcPid)
-			elog(ERROR, "cannot wait on a latch owned by another process");
-		set->latch = latch;
+		set->interrupt_mask = interruptMask;
 
 		/*
-		 * On Unix, we don't need to modify the kernel object because the
-		 * underlying pipe (if there is one) is the same for all latches so we
-		 * can return immediately.  On Windows, we need to update our array of
-		 * handles, but we leave the old one in place and tolerate spurious
-		 * wakeups if the latch is disabled.
+		 * We don't bother to adjust the event when the interrupt mask
+		 * changes.  It means that when interrupt mask is set to 0, we will
+		 * listen on the kernel object unnecessarily, and might get some
+		 * spurious wakeups. The interrupt sending code should not wake us up
+		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
+		 * should be rare.
 		 */
-#if defined(WAIT_USE_WIN32)
-		if (!latch)
-			return;
-#else
 		return;
-#endif
 	}
 
 #if defined(WAIT_USE_EPOLL)
@@ -745,9 +747,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)
@@ -794,9 +795,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)
@@ -858,9 +858,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;
@@ -886,8 +886,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 |
@@ -902,10 +901,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
 	{
@@ -985,10 +984,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)
 	{
@@ -1044,6 +1046,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);
 
@@ -1065,100 +1068,117 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 #ifndef WIN32
 	waiting = true;
 #else
-	/* Ensure that signals are serviced even if latch is already set */
+	/* Ensure that signals are serviced even if interrupt is already pending */
 	pgwin32_dispatch_queued_signals();
 #endif
-	while (returned_events == 0)
+
+	/*
+	 * Atomically check if the interrupt is already pending and advertise that
+	 * we are about to start sleeping. If it was already pending, avoid
+	 * blocking altogether.
+	 *
+	 * If someone sets the interrupt flag between this and the
+	 * WaitEventSetWaitBlock() below, the setter will write a byte to the pipe
+	 * (or signal us and the signal handler will do that), and the readiness
+	 * routine will return immediately.
+	 */
+	if (set->interrupt_mask != 0)
 	{
-		int			rc;
+		InterruptMask old_mask;
+		bool		already_pending = false;
 
 		/*
-		 * Check if the latch is set already first.  If so, we either exit
-		 * immediately or ask the kernel for further events available right
-		 * now without waiting, depending on how many events the caller wants.
-		 *
-		 * If someone sets the latch between this and the
-		 * WaitEventSetWaitBlock() below, the setter will write a byte to the
-		 * pipe (or signal us and the signal handler will do that), and the
-		 * readiness routine will return immediately.
-		 *
-		 * On unix, If there's a pending byte in the self pipe, we'll notice
-		 * whenever blocking. Only clearing the pipe in that case avoids
-		 * having to drain it every time WaitLatchOrSocket() is used. Should
-		 * the pipe-buffer fill up we're still ok, because the pipe is in
-		 * nonblocking mode. It's unlikely for that to happen, because the
-		 * self pipe isn't filled unless we're blocking (waiting = true), or
-		 * from inside a signal handler in latch_sigurg_handler().
-		 *
-		 * On windows, we'll also notice if there's a pending event for the
-		 * latch when blocking, but there's no danger of anything filling up,
-		 * as "Setting an event that is already set has no effect.".
-		 *
-		 * Note: we assume that the kernel calls involved in latch management
-		 * will provide adequate synchronization on machines with weak memory
-		 * ordering, so that we cannot miss seeing is_set if a notification
-		 * has already been queued.
+		 * Perform a plain atomic read first as a fast path for the case that
+		 * an interrupt is already pending.
 		 */
-		if (set->latch && !set->latch->is_set)
+		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
+		already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+		if (!already_pending)
 		{
-			/* about to sleep on a latch */
-			set->latch->maybe_sleeping = true;
-			pg_memory_barrier();
-			/* and recheck */
+			/*
+			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
+			 * an interrupt is already pending. The atomic op provides
+			 * synchronization so that if an interrupt bit is set after this,
+			 * the setter will wake us up.
+			 */
+			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
+			already_pending = ((old_mask & set->interrupt_mask) != 0);
+
+			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			sleeping_flag_armed = true;
 		}
 
-		if (set->latch && set->latch->is_set)
+		if (already_pending)
 		{
 			occurred_events->fd = PGINVALID_SOCKET;
-			occurred_events->pos = set->latch_pos;
+			occurred_events->pos = set->interrupt_pos;
 			occurred_events->user_data =
-				set->events[set->latch_pos].user_data;
-			occurred_events->events = WL_LATCH_SET;
+				set->events[set->interrupt_pos].user_data;
+			occurred_events->events = WL_INTERRUPT;
 			occurred_events++;
 			returned_events++;
 
-			/* could have been set above */
-			set->latch->maybe_sleeping = false;
-
-			if (returned_events == nevents)
-				break;			/* output buffer full already */
-
 			/*
 			 * Even though we already have an event, we'll poll just once with
-			 * zero timeout to see what non-latch events we can fit into the
-			 * output buffer at the same time.
+			 * zero timeout to see what non-interrupt events we can fit into
+			 * the output buffer at the same time.
 			 */
 			cur_timeout = 0;
 			timeout = 0;
 		}
+	}
 
+	if (nevents > returned_events)
+	{
 		/*
 		 * Wait for events using the readiness primitive chosen at the top of
 		 * this file. If -1 is returned, a timeout has occurred, if 0 we have
 		 * to retry, everything >= 1 is the number of returned events.
+		 *
+		 * On unix, If there's a pending byte in the self pipe, we'll notice
+		 * whenever blocking. Only clearing the pipe in that case avoids
+		 * having to drain it every time WaitInterruptOrSocket() is used.
+		 * Should the pipe-buffer fill up we're still ok, because the pipe is
+		 * in nonblocking mode. It's unlikely for that to happen, because the
+		 * self pipe isn't filled unless we're blocking (waiting = true), or
+		 * from inside a signal handler in interrupt_sigurg_handler().
+		 *
+		 * On windows, we'll also notice if there's a pending event for the
+		 * interrupt when blocking, but there's no danger of anything filling
+		 * up, as "Setting an event that is already set has no effect.".
 		 */
-		rc = WaitEventSetWaitBlock(set, cur_timeout,
-								   occurred_events, nevents - returned_events);
+		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;
+			}
 		}
 	}
+
+	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	if (sleeping_flag_armed)
+		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+
 #ifndef WIN32
 	waiting = false;
 #endif
@@ -1229,16 +1249,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_epoll_event->events & (EPOLLIN | EPOLLERR | EPOLLHUP))
 		{
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1391,13 +1411,13 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1513,16 +1533,16 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET &&
+		if (cur_event->events == WL_INTERRUPT &&
 			(cur_pollfd->revents & (POLLIN | POLLHUP | POLLERR | POLLNVAL)))
 		{
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1621,6 +1641,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
@@ -1726,19 +1755,15 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		occurred_events->user_data = cur_event->user_data;
 		occurred_events->events = 0;
 
-		if (cur_event->events == WL_LATCH_SET)
+		if (cur_event->events == WL_INTERRUPT)
 		{
-			/*
-			 * We cannot use set->latch->event to reset the fired event if we
-			 * aren't waiting on this latch now.
-			 */
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->latch && set->latch->maybe_sleeping && set->latch->is_set)
+			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
-				occurred_events->events = WL_LATCH_SET;
+				occurred_events->events = WL_INTERRUPT;
 				occurred_events++;
 				returned_events++;
 			}
@@ -1783,7 +1808,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
@@ -1889,18 +1914,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)
 {
@@ -1917,7 +1941,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;
@@ -2004,7 +2028,6 @@ ResOwnerReleaseWaitEventSet(Datum res)
 	FreeWaitEventSet(set);
 }
 
-#ifndef WIN32
 /*
  * Wake up my process if it's currently sleeping in WaitEventSetWaitBlock()
  *
@@ -2014,12 +2037,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();
@@ -2027,12 +2049,28 @@ WakeupMyProc(void)
 	if (waiting)
 		kill(MyProcPid, SIGURG);
 #endif
+#else
+	SetEvent(MyProc ? MyProc->interruptWakeupEvent : LocalInterruptWakeupEvent);
+#endif
 }
 
 /* Similar to WakeupMyProc, but wake up another process */
 void
-WakeupOtherProc(int pid)
+WakeupOtherProc(PGPROC *proc)
 {
-	kill(pid, SIGURG);
-}
+	/*
+	 * Note: This can also be called from the postmaster, so be careful to not
+	 * assume that the contents of shared memory are valid.  Reading the 'pid'
+	 * (or event handle on Windows) is safe enough.
+	 */
+#ifndef WIN32
+	kill(proc->pid, SIGURG);
+#else
+	SetEvent(proc->interruptWakeupEvent);
+
+	/*
+	 * Note that we silently ignore any errors. We might be in a signal
+	 * handler or other critical path where it's not safe to call elog().
+	 */
 #endif
+}
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..0ba836cf9f3 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,6 +20,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
@@ -149,23 +150,24 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		INSTR_TIME_SET_CURRENT(start_time);
 		Assert(timeout >= 0 && timeout <= INT_MAX);
 		cur_timeout = timeout;
-		wait_events = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 	}
 	else
-		wait_events = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH;
+		wait_events = WL_INTERRUPT | WL_EXIT_ON_PM_DEATH;
 
 	while (true)
 	{
 		bool		done = false;
 
 		/*
-		 * Wait for latch to be set.  (If we're awakened for some other
-		 * reason, the code below will cope anyway.)
+		 * Wait for interrupt.  (If we're awakened for some other reason, the
+		 * code below will cope anyway.)
 		 */
-		(void) WaitLatch(MyLatch, wait_events, cur_timeout, wait_event_info);
+		(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+							 wait_events, cur_timeout, wait_event_info);
 
-		/* Reset latch before examining the state of the wait list. */
-		ResetLatch(MyLatch);
+		/* Clear the flag before examining the state of the wait list. */
+		ClearInterrupt(INTERRUPT_WAIT_WAKEUP);
 
 		/*
 		 * If this process has been taken out of the wait list, then we know
@@ -178,9 +180,10 @@ ConditionVariableTimedSleep(ConditionVariable *cv, long timeout,
 		 * the wait list only when the caller calls
 		 * ConditionVariableCancelSleep.
 		 *
-		 * If we're still in the wait list, then the latch must have been set
-		 * by something other than ConditionVariableSignal; though we don't
-		 * guarantee not to return spuriously, we'll avoid this obvious case.
+		 * If we're still in the wait list, then the interrupt must have been
+		 * sent by something other than ConditionVariableSignal; though we
+		 * don't guarantee not to return spuriously, we'll avoid this obvious
+		 * case.
 		 */
 		SpinLockAcquire(&cv->mutex);
 		if (!proclist_contains(&cv->wakeup, MyProcNumber, cvWaitLink))
@@ -268,9 +271,9 @@ ConditionVariableSignal(ConditionVariable *cv)
 		proc = proclist_pop_head_node(&cv->wakeup, cvWaitLink);
 	SpinLockRelease(&cv->mutex);
 
-	/* If we found someone sleeping, set their latch to wake them up. */
+	/* If we found someone sleeping, wake them up. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 }
 
 /*
@@ -299,8 +302,8 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 	 * CV and in doing so remove our sentinel entry.  But that's fine: since
 	 * CV waiters are always added and removed in order, that must mean that
 	 * every previous waiter has been wakened, so we're done.  We'll get an
-	 * extra "set" on our latch from the someone else's signal, which is
-	 * slightly inefficient but harmless.
+	 * extra interrupt from the someone else's signal, which is slightly
+	 * inefficient but harmless.
 	 *
 	 * We can't insert our cvWaitLink as a sentinel if it's already in use in
 	 * some other proclist.  While that's not expected to be true for typical
@@ -333,7 +336,7 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 
 	/* Awaken first waiter, if there was one. */
 	if (proc != NULL)
-		SetLatch(&proc->procLatch);
+		SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 
 	while (have_sentinel)
 	{
@@ -357,6 +360,6 @@ ConditionVariableBroadcast(ConditionVariable *cv)
 		SpinLockRelease(&cv->mutex);
 
 		if (proc != NULL && proc != MyProc)
-			SetLatch(&proc->procLatch);
+			SendInterrupt(INTERRUPT_WAIT_WAKEUP, GetNumberFromPGProc(proc));
 	}
 }
diff --git a/src/backend/storage/lmgr/predicate.c b/src/backend/storage/lmgr/predicate.c
index fe75ead3501..f961054a4ba 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -204,6 +204,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "port/pg_lfind.h"
@@ -1584,7 +1585,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;
@@ -3617,7 +3622,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 e001cb11803..892e83eecbd 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -37,6 +37,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
@@ -214,6 +215,8 @@ InitProcGlobal(void)
 	pg_atomic_init_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->walreceiverProc, INVALID_PROC_NUMBER);
+	pg_atomic_init_u32(&ProcGlobal->startupProc, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -301,14 +304,28 @@ InitProcGlobal(void)
 		Assert(fpPtr <= fpEndPtr);
 
 		/*
-		 * Set up per-PGPROC semaphore, latch, and fpInfoLock.  Prepared xact
-		 * dummy PGPROCs don't need these though - they're never associated
-		 * with a real process
+		 * Set up per-PGPROC semaphore, interrupt wakeup event (on Windows),
+		 * and fpInfoLock.  Prepared xact dummy PGPROCs don't need these
+		 * though - they're never associated with a real process
 		 */
 		if (i < FIRST_PREPARED_XACT_PROC_NUMBER)
 		{
+#ifdef WIN32
+			SECURITY_ATTRIBUTES sa;
+
+			/*
+			 * Set up security attributes to specify that the events are
+			 * inherited.
+			 */
+			ZeroMemory(&sa, sizeof(sa));
+			sa.nLength = sizeof(sa);
+			sa.bInheritHandle = TRUE;
+
+			proc->interruptWakeupEvent = CreateEvent(&sa, TRUE, FALSE, NULL);
+			if (proc->interruptWakeupEvent == NULL)
+				elog(ERROR, "CreateEvent failed: error code %lu", GetLastError());
+#endif
 			proc->sem = PGSemaphoreCreate();
-			InitSharedLatch(&(proc->procLatch));
 			LWLockInitialize(&(proc->fpInfoLock), LWTRANCHE_LOCK_FASTPATH);
 		}
 
@@ -519,13 +536,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);
@@ -690,13 +702,8 @@ InitAuxiliaryProcess(void)
 	}
 #endif
 
-	/*
-	 * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch
-	 * on it.  That allows us to repoint the process latch, which so far
-	 * points to process local one, to the shared one.
-	 */
-	OwnLatch(&MyProc->procLatch);
-	SwitchToSharedLatch();
+	/* Start accepting interrupts from other processes */
+	SwitchToSharedInterrupts();
 
 	/* now that we have a proc, report wait events to shared memory */
 	pgstat_set_wait_event_storage(&MyProc->wait_event_info);
@@ -719,6 +726,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.
@@ -988,21 +999,20 @@ ProcKill(int code, Datum arg)
 	}
 
 	/*
-	 * Reset MyLatch to the process local one.  This is so that signal
-	 * handlers et al can continue using the latch after the shared latch
-	 * isn't ours anymore.
+	 * Reset interrupt vector to the process local one.  This is so that
+	 * signal handlers et al can continue using interrupts after the PGPROC
+	 * entry isn't ours anymore.
 	 *
 	 * Similarly, stop reporting wait events to MyProc->wait_event_info.
 	 *
-	 * After that clear MyProc and disown the shared latch.
+	 * After that clear MyProc.
 	 */
-	SwitchBackToLocalLatch();
+	SwitchToLocalInterrupts();
 	pgstat_reset_wait_event_storage();
 
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
-	DisownLatch(&proc->procLatch);
 
 	/* Mark the proc no longer in use */
 	proc->pid = 0;
@@ -1061,7 +1071,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 */
@@ -1080,11 +1090,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);
 
@@ -1412,18 +1431,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
 	{
@@ -1469,9 +1488,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)
 			{
@@ -1677,8 +1697,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)
 	{
@@ -1715,7 +1735,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.
  *
@@ -1744,7 +1764,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));
 }
 
 /*
@@ -1900,14 +1920,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;
 }
 
@@ -1986,34 +2004,6 @@ GetLockHoldersAndWaiters(LOCALLOCK *locallock, StringInfo lock_holders_sbuf,
 	}
 }
 
-/*
- * ProcWaitForSignal - wait for a signal from another backend.
- *
- * As this uses the generic process latch the caller has to be robust against
- * unrelated wakeups: Always check that the desired state has occurred, and
- * wait again if not.
- */
-void
-ProcWaitForSignal(uint32 wait_event_info)
-{
-	(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-					 wait_event_info);
-	ResetLatch(MyLatch);
-	CHECK_FOR_INTERRUPTS();
-}
-
-/*
- * ProcSendSignal - set the latch of a backend identified by ProcNumber
- */
-void
-ProcSendSignal(ProcNumber procNumber)
-{
-	if (procNumber < 0 || procNumber >= ProcGlobal->allProcCount)
-		elog(ERROR, "procNumber out of range");
-
-	SetLatch(&GetPGProcByNumber(procNumber)->procLatch);
-}
-
 /*
  * BecomeLockGroupLeader - designate process as lock group leader
  *
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 5391640d861..2dd80e36fdc 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -994,7 +994,7 @@ smgrfd(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, uint32 *off)
 	 * The caller needs to prevent interrupts from being processed, otherwise
 	 * the FD could be closed prematurely.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	fd = smgrsw[reln->smgr_which].smgr_fd(reln, forknum, blocknum, off);
 
@@ -1073,7 +1073,7 @@ smgr_aio_reopen(PgAioHandle *ioh)
 	 * The caller needs to prevent interrupts from being processed, otherwise
 	 * the FD could be closed again before we get to executing the IO.
 	 */
-	Assert(!INTERRUPTS_CAN_BE_PROCESSED());
+	Assert(CheckForInterruptsMask == 0);
 
 	if (sd->smgr.is_temp)
 		procno = pgaio_io_get_owner(ioh);
diff --git a/src/backend/storage/sync/sync.c b/src/backend/storage/sync/sync.c
index b1accc68b95..fbed7d82e98 100644
--- a/src/backend/storage/sync/sync.c
+++ b/src/backend/storage/sync/sync.c
@@ -22,12 +22,12 @@
 #include "access/commit_ts.h"
 #include "access/multixact.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/bgwriter.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "storage/md.h"
 #include "utils/hsearch.h"
 #include "utils/memutils.h"
@@ -611,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 c517115927c..9595476c8e0 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -198,6 +198,14 @@ BackendInitialize(ClientSocket *client_sock, CAC_state cac)
 	InitializeTimeouts();		/* establishes SIGALRM handler */
 	sigprocmask(SIG_SETMASK, &StartupBlockSig, NULL);
 
+	/*
+	 * FIXME: install INTERRUPT_TERMINATE handler at least? Not really needed
+	 * because no one else can send us interrupts yet, and we don't raise them
+	 * within the backend yet either. But if we changed postmaster to send
+	 * INTERRUPT_TERMINATE directly, for example, we'd need to handle it
+	 * already.
+	 */
+
 	/*
 	 * Get the remote host name and port for logging and status display.
 	 */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5d66c80567c..5e595e51987 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -40,6 +40,8 @@
 #include "commands/explain_state.h"
 #include "commands/prepare.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -54,7 +56,6 @@
 #include "pg_getopt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
@@ -179,8 +180,8 @@ static bool IsTransactionExitStmt(Node *parsetree);
 static bool IsTransactionExitStmtList(List *pstmts);
 static bool IsTransactionStmtList(List *pstmts);
 static void drop_unnamed_stmt(void);
-static void ProcessRecoveryConflictInterrupts(void);
-static void ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason);
+static void ProcessRecoveryConflictInterrupt(void);
+static void ProcessRecoveryConflict(RecoveryConflictReason reason);
 static void report_recovery_conflict(RecoveryConflictReason reason);
 static void log_disconnections(int code, Datum arg);
 static void enable_statement_timeout(void);
@@ -335,8 +336,6 @@ interactive_getc(void)
 
 	c = getc(stdin);
 
-	ProcessClientReadInterrupt(false);
-
 	return c;
 }
 
@@ -353,11 +352,23 @@ SocketBackend(StringInfo inBuf)
 {
 	int			qtype;
 	int			maxmsglen;
+	bool		save_query_cancel_enabled;
+
+	/*
+	 * Don't allow query cancel interrupts while reading input from the
+	 * client, because we might lose sync in the FE/BE protocol.  (Die
+	 * interrupts are OK, because we won't read any further messages from the
+	 * client in that case.)
+	 *
+	 * See similar logic in ProcessRecoveryConflictInterrupt().
+	 */
+	save_query_cancel_enabled = (EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) != 0;
+	if (save_query_cancel_enabled)
+		DisableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	/*
 	 * Get message type code from the frontend.
 	 */
-	HOLD_CANCEL_INTERRUPTS();
 	pq_startmsgread();
 	qtype = pq_getbyte();
 
@@ -464,7 +475,9 @@ SocketBackend(StringInfo inBuf)
 	 */
 	if (pq_getmessage(inBuf, maxmsglen))
 		return EOF;				/* suitable message already logged */
-	RESUME_CANCEL_INTERRUPTS();
+
+	if (save_query_cancel_enabled)
+		EnableInterrupt(INTERRUPT_QUERY_CANCEL);
 
 	return qtype;
 }
@@ -488,104 +501,6 @@ ReadCommand(StringInfo inBuf)
 	return result;
 }
 
-/*
- * ProcessClientReadInterrupt() - Process interrupts specific to client reads
- *
- * This is called just before and after low-level reads.
- * 'blocked' is true if no data was available to read and we plan to retry,
- * false if about to read or done reading.
- *
- * Must preserve errno!
- */
-void
-ProcessClientReadInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (DoingCommandRead)
-	{
-		/* Check for general interrupts that arrived before/while reading */
-		CHECK_FOR_INTERRUPTS();
-
-		/* Process sinval catchup interrupts, if any */
-		if (catchupInterruptPending)
-			ProcessCatchupInterrupt();
-
-		/* Process notify interrupts, if any */
-		if (notifyInterruptPending)
-			ProcessNotifyInterrupt(true);
-	}
-	else if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If there is no data available to read, then it's safe
-		 * (and sane) to handle that now.  If we haven't tried to read yet,
-		 * make sure the process latch is set, so that if there is no data
-		 * then we'll come back here and die.  If we're done reading, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while reading.
-		 */
-		if (blocked)
-			CHECK_FOR_INTERRUPTS();
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
-/*
- * ProcessClientWriteInterrupt() - Process interrupts specific to client writes
- *
- * This is called just before and after low-level writes.
- * 'blocked' is true if no data could be written and we plan to retry,
- * false if about to write or done writing.
- *
- * Must preserve errno!
- */
-void
-ProcessClientWriteInterrupt(bool blocked)
-{
-	int			save_errno = errno;
-
-	if (ProcDiePending)
-	{
-		/*
-		 * We're dying.  If it's not possible to write, then we should handle
-		 * that immediately, else a stuck client could indefinitely delay our
-		 * response to the signal.  If we haven't tried to write yet, make
-		 * sure the process latch is set, so that if the write would block
-		 * then we'll come back here and die.  If we're done writing, also
-		 * make sure the process latch is set, as we might've undesirably
-		 * cleared it while writing.
-		 */
-		if (blocked)
-		{
-			/*
-			 * Don't mess with whereToSendOutput if ProcessInterrupts wouldn't
-			 * service ProcDiePending.
-			 */
-			if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-			{
-				/*
-				 * We don't want to send the client the error message, as a)
-				 * that would possibly block again, and b) it would likely
-				 * lead to loss of protocol sync because we may have already
-				 * sent a partial protocol message.
-				 */
-				if (whereToSendOutput == DestRemote)
-					whereToSendOutput = DestNone;
-
-				CHECK_FOR_INTERRUPTS();
-			}
-		}
-		else
-			SetLatch(MyLatch);
-	}
-
-	errno = save_errno;
-}
-
 /*
  * Do raw parsing (only).
  *
@@ -2910,7 +2825,7 @@ drop_unnamed_stmt(void)
  * Either some backend has bought the farm, or we've been told to shut down
  * "immediately"; so we need to stop what we're doing and exit.
  */
-void
+static void
 quickdie(SIGNAL_ARGS)
 {
 	sigaddset(&BlockSig, SIGQUIT);	/* prevent nested calls */
@@ -3007,50 +2922,26 @@ quickdie(SIGNAL_ARGS)
  * Shutdown signal from postmaster: abort transaction and exit
  * at soonest convenient time
  */
-void
+static void
 die(SIGNAL_ARGS)
 {
-	/* Don't joggle the elbow of proc_exit */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		ProcDiePending = true;
-	}
-
 	/* for the cumulative stats system */
 	pgStatSessionEndCause = DISCONNECT_KILLED;
 
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+	/* Don't joggle the elbow of proc_exit */
+	if (proc_exit_inprogress)
+		return;
+
+	RaiseInterrupt(INTERRUPT_TERMINATE);
 
 	/*
 	 * If we're in single user mode, we want to quit immediately - we can't
-	 * rely on latches as they wouldn't work when stdin/stdout is a file.
+	 * rely on interrupts as they wouldn't work when stdin/stdout is a file.
 	 * Rather ugly, but it's unlikely to be worthwhile to invest much more
 	 * effort just for the benefit of single user mode.
 	 */
 	if (DoingCommandRead && whereToSendOutput != DestRemote)
-		ProcessInterrupts();
-}
-
-/*
- * Query-cancel signal from postmaster: abort current transaction
- * at soonest convenient time
- */
-void
-StatementCancelHandler(SIGNAL_ARGS)
-{
-	/*
-	 * Don't joggle the elbow of proc_exit
-	 */
-	if (!proc_exit_inprogress)
-	{
-		InterruptPending = true;
-		QueryCancelPending = true;
-	}
-
-	/* If we're still here, waken anything waiting on the process latch */
-	SetLatch(MyLatch);
+		ProcessTerminateInterrupt();
 }
 
 /* signal handler for floating point exception */
@@ -3067,22 +2958,91 @@ FloatExceptionHandler(SIGNAL_ARGS)
 }
 
 /*
- * Tell the next CHECK_FOR_INTERRUPTS() to process recovery conflicts.  Runs
- * in a SIGUSR1 handler.
+ * Set the "standard" set of interrupt handlers, to handle common interrupts
+ * that should be handled by all or most child processes.
+ *
+ * The caller may modify the defaults after calling this with further
+ * SetInterruptHandler() or Enable/DisableInterrupt() calls.
  */
 void
-HandleRecoveryConflictInterrupt(void)
+SetStandardInterruptHandlers(void)
 {
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		InterruptPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
+	/* All processes should react to barriers and memory context debugging */
+	SetInterruptHandler(INTERRUPT_BARRIER, ProcessProcSignalBarrier);
+	EnableInterrupt(INTERRUPT_BARRIER);
+	SetInterruptHandler(INTERRUPT_LOG_MEMORY_CONTEXT, ProcessLogMemoryContextInterrupt);
+	EnableInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT);
+
+	/*
+	 * Catchup interrupts must be handled in anything that participates in
+	 * shared invalidation
+	 */
+	/* XXX: done in sinvaladt.c */
+	/* SetInterruptHandler(INTERRUPT_SINVAL_CATCHUP, ProcessCatchupInterrupt); */
+
+	/*
+	 * Every process should react to INTERRUPT_TERMINATE. But many processes
+	 * disable this and do their own checks at appropriate times.
+	 */
+	SetInterruptHandler(INTERRUPT_TERMINATE, ProcessTerminateInterrupt);
+	EnableInterrupt(INTERRUPT_TERMINATE);
+
+	/*
+	 * Backends and processes that connect to a particular database can
+	 * conflict with recovery
+	 *
+	 * xxx: Set this in InitPostgres if we're connecting to a particular
+	 * database?
+	 */
+	SetInterruptHandler(INTERRUPT_RECOVERY_CONFLICT, ProcessRecoveryConflictInterrupt);
+	EnableInterrupt(INTERRUPT_RECOVERY_CONFLICT);
+
+	/*
+	 * All processes should handle config reloads. But usually only in
+	 * specific spots, like when not in a transaction, so this is disabled by
+	 * default.
+	 *
+	 * This relies on the SIGHUP signal handler to raise
+	 * INTERRUPT_CONFIG_RELOAD on SIGHUP.
+	 */
+	SetInterruptHandler(INTERRUPT_CONFIG_RELOAD, ProcessConfigReloadInterrupt);
 }
 
 /*
- * Check one individual conflict reason.
+ * Check each possible recovery conflict reason.
  */
 static void
-ProcessRecoveryConflictInterrupt(RecoveryConflictReason reason)
+ProcessRecoveryConflictInterrupt(void)
+{
+	uint32		pending;
+
+	/* Are any recovery conflicts pending? */
+	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
+	if (pending == 0)
+		return;
+
+	/*
+	 * Check the conflicts one by one, clearing each flag only before
+	 * processing the particular conflict.  This ensures that if multiple
+	 * conflicts are pending, we come back here to process the remaining
+	 * conflicts, if an error is thrown during processing one of them.
+	 */
+	for (RecoveryConflictReason reason = 0;
+		 reason < NUM_RECOVERY_CONFLICT_REASONS;
+		 reason++)
+	{
+		if ((pending & (1 << reason)) != 0)
+		{
+			/* clear the flag */
+			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
+
+			ProcessRecoveryConflict(reason);
+		}
+	}
+}
+
+void
+ProcessRecoveryConflict(RecoveryConflictReason reason)
 {
 	switch (reason)
 	{
@@ -3242,14 +3202,14 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (QueryCancelHoldoffCount != 0)
+			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
 				 * code in ProcessInterrupts().
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
-				InterruptPending = true;
+				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
 				return;
 			}
 
@@ -3280,72 +3240,31 @@ report_recovery_conflict(RecoveryConflictReason reason)
 					 " database and repeat your command.")));
 }
 
-/*
- * Check each possible recovery conflict reason.
- */
-static void
-ProcessRecoveryConflictInterrupts(void)
+void
+ProcessConfigReloadInterrupt(void)
 {
-	uint32		pending;
-
-	/*
-	 * We don't need to worry about joggling the elbow of proc_exit, because
-	 * proc_exit_prepare() holds interrupts, so ProcessInterrupts() won't call
-	 * us.
-	 */
-	Assert(!proc_exit_inprogress);
-	Assert(InterruptHoldoffCount == 0);
-
-	/* Are any recovery conflict pending? */
-	pending = pg_atomic_read_membarrier_u32(&MyProc->pendingRecoveryConflicts);
-	if (pending == 0)
-		return;
+	ProcessConfigFile(PGC_SIGHUP);
+}
 
+void
+ProcessAsyncNotifyInterrupt(void)
+{
 	/*
-	 * Check the conflicts one by one, clearing each flag only before
-	 * processing the particular conflict.  This ensures that if multiple
-	 * conflicts are pending, we come back here to process the remaining
-	 * conflicts, if an error is thrown during processing one of them.
+	 * This is only enabled while DoingCommandRead, so we want to flush the
+	 * async notify to the client immediately
 	 */
-	for (RecoveryConflictReason reason = 0;
-		 reason < NUM_RECOVERY_CONFLICT_REASONS;
-		 reason++)
-	{
-		if ((pending & (1 << reason)) != 0)
-		{
-			/* clear the flag */
-			(void) pg_atomic_fetch_and_u32(&MyProc->pendingRecoveryConflicts, ~(1 << reason));
-
-			ProcessRecoveryConflictInterrupt(reason);
-		}
-	}
+	Assert(DoingCommandRead);
+	ProcessNotifyInterrupt(true);
 }
 
-/*
- * ProcessInterrupts: out-of-line portion of CHECK_FOR_INTERRUPTS() macro
- *
- * If an interrupt condition is pending, and it's safe to service it,
- * then clear the flag and accept the interrupt.  Called only when
- * InterruptPending is true.
- *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED() is true, then ProcessInterrupts
- * is guaranteed to clear the InterruptPending flag before returning.
- * (This is not the same as guaranteeing that it's still clear when we
- * return; another interrupt could have arrived.  But we promise that
- * any pre-existing one will have been serviced.)
- */
 void
-ProcessInterrupts(void)
+ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	if (InterruptHoldoffCount != 0 || CritSectionCount != 0)
-		return;
-	InterruptPending = false;
+	Assert(InterruptHoldoffCount == 0);
+	Assert(CritSectionCount == 0);
 
-	if (ProcDiePending)
 	{
-		ProcDiePending = false;
-		QueryCancelPending = false; /* ProcDie trumps QueryCancel */
 		LockErrorCleanup();
 		/* As in quickdie, don't risk sending to client during auth */
 		if (ClientAuthInProgress && whereToSendOutput == DestRemote)
@@ -3394,65 +3313,55 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+}
 
-	if (CheckClientConnectionPending)
-	{
-		CheckClientConnectionPending = false;
-
-		/*
-		 * Check for lost connection and re-arm, if still configured, but not
-		 * if we've arrived back at DoingCommandRead state.  We don't want to
-		 * wake up idle sessions, and they already know how to detect lost
-		 * connections.
-		 */
-		if (!DoingCommandRead && client_connection_check_interval > 0)
-		{
-			if (!pq_check_connection())
-				ClientConnectionLost = true;
-			else
-				enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
-									 client_connection_check_interval);
-		}
-	}
 
-	if (ClientConnectionLost)
+void
+ProcessClientCheckTimeoutInterrupt(void)
+{
+	/*
+	 * Check for lost connection and re-arm, if still configured, but not if
+	 * we've arrived back at DoingCommandRead state.  We don't want to wake up
+	 * idle sessions, and they already know how to detect lost connections.
+	 */
+	if (!DoingCommandRead && client_connection_check_interval > 0)
 	{
-		QueryCancelPending = false; /* lost connection trumps QueryCancel */
-		LockErrorCleanup();
-		/* don't send to client, we already know the connection to be dead. */
-		whereToSendOutput = DestNone;
-		ereport(FATAL,
-				(errcode(ERRCODE_CONNECTION_FAILURE),
-				 errmsg("connection to client lost")));
+		if (!pq_check_connection())
+			RaiseInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+		else
+			enable_timeout_after(CLIENT_CONNECTION_CHECK_TIMEOUT,
+								 client_connection_check_interval);
 	}
+}
+
+void
+ProcessClientConnectionLost(void)
+{
+	/* FIXME: check procdie first */
 
+	ClearInterrupt(INTERRUPT_QUERY_CANCEL); /* lost connection trumps
+											 * QueryCancel */
+	LockErrorCleanup();
+	/* don't send to client, we already know the connection to be dead. */
+	whereToSendOutput = DestNone;
+	ereport(FATAL,
+			(errcode(ERRCODE_CONNECTION_FAILURE),
+			 errmsg("connection to client lost")));
+}
+
+void
+ProcessQueryCancelInterrupt(void)
+{
 	/*
-	 * Don't allow query cancel interrupts while reading input from the
-	 * client, because we might lose sync in the FE/BE protocol.  (Die
-	 * interrupts are OK, because we won't read any further messages from the
-	 * client in that case.)
-	 *
-	 * See similar logic in ProcessRecoveryConflictInterrupts().
+	 * FIXME: Check client connection lost and terminate interrupts first, so
+	 * that if we're about to terminate the whole backend anyway, we do that
+	 * straight away and skip the query cancellation.
 	 */
-	if (QueryCancelPending && QueryCancelHoldoffCount != 0)
-	{
-		/*
-		 * Re-arm InterruptPending so that we process the cancel request as
-		 * soon as we're done reading the message.  (XXX this is seriously
-		 * ugly: it complicates INTERRUPTS_CAN_BE_PROCESSED(), and it means we
-		 * can't use that macro directly as the initial test in this function,
-		 * meaning that this code also creates opportunities for other bugs to
-		 * appear.)
-		 */
-		InterruptPending = true;
-	}
-	else if (QueryCancelPending)
+
 	{
 		bool		lock_timeout_occurred;
 		bool		stmt_timeout_occurred;
 
-		QueryCancelPending = false;
-
 		/*
 		 * If LOCK_TIMEOUT and STATEMENT_TIMEOUT indicators are both set, we
 		 * need to clear both, so always fetch both.
@@ -3505,76 +3414,66 @@ ProcessInterrupts(void)
 					 errmsg("canceling statement due to user request")));
 		}
 	}
+}
 
-	if (pg_atomic_read_u32(&MyProc->pendingRecoveryConflicts) != 0)
-		ProcessRecoveryConflictInterrupts();
 
-	if (IdleInTransactionSessionTimeoutPending)
+void
+ProcessIdleInTransactionSessionTimeoutInterrupt(void)
+{
+	/*
+	 * If the GUC has been reset to zero, ignore the interrupt.  This is
+	 * important because the GUC update itself won't disable any pending
+	 * interrupt.  We need to unset the flag before the injection point,
+	 * otherwise we could loop in interrupts checking.
+	 */
+	if (IdleInTransactionSessionTimeout > 0)
 	{
-		/*
-		 * If the GUC has been reset to zero, ignore the signal.  This is
-		 * important because the GUC update itself won't disable any pending
-		 * interrupt.  We need to unset the flag before the injection point,
-		 * otherwise we could loop in interrupts checking.
-		 */
-		IdleInTransactionSessionTimeoutPending = false;
-		if (IdleInTransactionSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-in-transaction timeout")));
-		}
+		INJECTION_POINT("idle-in-transaction-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-in-transaction timeout")));
 	}
+}
 
-	if (TransactionTimeoutPending)
+void
+ProcessTransactionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (TransactionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		TransactionTimeoutPending = false;
-		if (TransactionTimeout > 0)
-		{
-			INJECTION_POINT("transaction-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
-					 errmsg("terminating connection due to transaction timeout")));
-		}
+		INJECTION_POINT("transaction-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_TRANSACTION_TIMEOUT),
+				 errmsg("terminating connection due to transaction timeout")));
 	}
+}
 
-	if (IdleSessionTimeoutPending)
+void
+ProcessIdleSessionTimeoutInterrupt(void)
+{
+	/* As above, ignore the signal if the GUC has been reset to zero. */
+	if (IdleSessionTimeout > 0)
 	{
-		/* As above, ignore the signal if the GUC has been reset to zero. */
-		IdleSessionTimeoutPending = false;
-		if (IdleSessionTimeout > 0)
-		{
-			INJECTION_POINT("idle-session-timeout", NULL);
-			ereport(FATAL,
-					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
-					 errmsg("terminating connection due to idle-session timeout")));
-		}
+		INJECTION_POINT("idle-session-timeout", NULL);
+		ereport(FATAL,
+				(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
+				 errmsg("terminating connection due to idle-session timeout")));
 	}
+}
 
+void
+ProcessIdleStatsTimeoutInterrupt(void)
+{
 	/*
-	 * If there are pending stats updates and we currently are truly idle
-	 * (matching the conditions in PostgresMain(), report stats now.
+	 * Stats update was requested. This handler is only enabled when we're
+	 * truly idle, with no transaction in progress
 	 */
-	if (IdleStatsUpdateTimeoutPending &&
-		DoingCommandRead && !IsTransactionOrTransactionBlock())
-	{
-		IdleStatsUpdateTimeoutPending = false;
-		pgstat_report_stat(true);
-	}
-
-	if (ProcSignalBarrierPending)
-		ProcessProcSignalBarrier();
-
-	if (ParallelMessagePending)
-		ProcessParallelMessages();
-
-	if (LogMemoryContextPending)
-		ProcessLogMemoryContextInterrupt();
-
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
+	/*
+	 * FIXME: check that this is called under right conditions also in
+	 * walsenders
+	 */
+	Assert(!IsTransactionOrTransactionBlock());
+	pgstat_report_stat(true);
 }
 
 /*
@@ -4238,9 +4137,13 @@ PostgresMain(const char *dbname, const char *username)
 
 	Assert(GetProcessingMode() == InitProcessing);
 
+	HOLD_INTERRUPTS();
+	SetStandardInterruptHandlers();
+
 	/*
-	 * Set up signal handlers.  (InitPostmasterChild or InitStandaloneProcess
-	 * has already set up BlockSig and made that the active signal mask.)
+	 * Set up signal and interrupt handlers.  (InitPostmasterChild or
+	 * InitStandaloneProcess has already set up BlockSig and made that the
+	 * active signal mask.)
 	 *
 	 * Note that postmaster blocked all signals before forking child process,
 	 * so there is no race condition whereby we might receive a signal before
@@ -4253,13 +4156,25 @@ PostgresMain(const char *dbname, const char *username)
 	 * an issue for signals that are locally generated, such as SIGALRM and
 	 * SIGPIPE.)
 	 */
+	SetInterruptHandler(INTERRUPT_QUERY_CANCEL, ProcessQueryCancelInterrupt);
+	EnableInterrupt(INTERRUPT_QUERY_CANCEL);
+
 	if (am_walsender)
+	{
+		pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
+
 		WalSndSignals();
+	}
 	else
 	{
-		pqsignal(SIGHUP, SignalHandlerForConfigReload);
-		pqsignal(SIGINT, StatementCancelHandler);	/* cancel current query */
-		pqsignal(SIGTERM, die); /* cancel current query and exit */
+		pqsignal(SIGINT, SignalHandlerForQueryCancel);	/* cancel current query */
+
+		/*
+		 * Cancel current query and exit. This is a bit more complicated in
+		 * backend processes. so we we cannot use
+		 * SignalHandlerForShutdownRequest here.
+		 */
+		pqsignal(SIGTERM, die); /* */
 
 		/*
 		 * In a postmaster child backend, replace SignalHandlerForCrashExit
@@ -4273,7 +4188,14 @@ PostgresMain(const char *dbname, const char *username)
 			pqsignal(SIGQUIT, quickdie);	/* hard crash time */
 		else
 			pqsignal(SIGQUIT, die); /* cancel current query and exit */
+
 		InitializeTimeouts();	/* establishes SIGALRM handler */
+		SetInterruptHandler(INTERRUPT_TRANSACTION_TIMEOUT, ProcessTransactionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_SESSION_TIMEOUT, ProcessIdleSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
+		SetInterruptHandler(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT, ProcessIdleInTransactionSessionTimeoutInterrupt);
+		EnableInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 
 		/*
 		 * Ignore failure to write to frontend. Note: if frontend closes
@@ -4282,11 +4204,31 @@ PostgresMain(const char *dbname, const char *username)
 		 * midst of output during who-knows-what operation...
 		 */
 		pqsignal(SIGPIPE, SIG_IGN);
-		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
-		pqsignal(SIGUSR2, SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
 	}
 
+	SetInterruptHandler(INTERRUPT_CLIENT_CHECK_TIMEOUT, ProcessClientCheckTimeoutInterrupt);
+	SetInterruptHandler(INTERRUPT_CLIENT_CONNECTION_LOST, ProcessClientConnectionLost);
+	EnableInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
+	EnableInterrupt(INTERRUPT_CLIENT_CONNECTION_LOST);
+
+	/* stats updates are only sent when we're idle, so don't enable this yet */
+	SetInterruptHandler(INTERRUPT_IDLE_STATS_TIMEOUT, ProcessIdleStatsTimeoutInterrupt);
+
+	/*
+	 * Backends that can LISTEN need this. (It is only enabled when idle
+	 * outside a transaction, however.)
+	 *
+	 * FIXME: should bgworkers do this too? Or it's up to them to set up the
+	 * handler if they LISTEN?
+	 */
+	SetInterruptHandler(INTERRUPT_ASYNC_NOTIFY, ProcessAsyncNotifyInterrupt);
+
+	SetInterruptHandler(INTERRUPT_PARALLEL_MESSAGE, ProcessParallelMessages);
+	EnableInterrupt(INTERRUPT_PARALLEL_MESSAGE);
+
+	RESUME_INTERRUPTS();
+
 	/* Early initialization */
 	BaseInit();
 
@@ -4454,11 +4396,14 @@ PostgresMain(const char *dbname, const char *username)
 		 * forgetting a timeout cancel.
 		 */
 		disable_all_timeouts(false);	/* do first to avoid race condition */
-		QueryCancelPending = false;
+		ClearInterrupt(INTERRUPT_QUERY_CANCEL);
 		idle_in_transaction_timeout_enabled = false;
 		idle_session_timeout_enabled = false;
 
 		/* Not reading from the client anymore. */
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 		DoingCommandRead = false;
 
 		/* Make sure libpq is in a good state */
@@ -4639,7 +4584,7 @@ PostgresMain(const char *dbname, const char *username)
 				 * were received during the just-finished transaction, they'll
 				 * be seen by the client before ReadyForQuery is.
 				 */
-				if (notifyInterruptPending)
+				if (ConsumeInterrupt(INTERRUPT_ASYNC_NOTIFY))
 					ProcessNotifyInterrupt(false);
 
 				/*
@@ -4728,6 +4673,28 @@ PostgresMain(const char *dbname, const char *username)
 		 */
 		DoingCommandRead = true;
 
+		/*
+		 * Let cache inval catchup requests to be processed while we're idle.
+		 */
+		EnableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+
+		/*
+		 * When we're truly idle, i.e. no transaction in progress, we can
+		 * additionally process any async LISTEN/NOTIFY messages and stats
+		 * update requests.
+		 */
+		if (!IsTransactionOrTransactionBlock())
+		{
+			EnableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+			EnableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
+		}
+
+		/*
+		 * If any of the newly-enabled interrupts are already pending, process
+		 * them now.
+		 */
+		CHECK_FOR_INTERRUPTS();
+
 		/*
 		 * (3) read a command (loop blocks here)
 		 */
@@ -4757,22 +4724,22 @@ PostgresMain(const char *dbname, const char *username)
 		 *
 		 * Query cancel is supposed to be a no-op when there is no query in
 		 * progress, so if a query cancel arrived while we were idle, just
-		 * reset QueryCancelPending. ProcessInterrupts() has that effect when
-		 * it's called when DoingCommandRead is set, so check for interrupts
-		 * before resetting DoingCommandRead.
+		 * reset INTERRUPT_QUERY_CANCEL. ProcessInterrupts() has that effect
+		 * when it's called when DoingCommandRead is set, so check for
+		 * interrupts before resetting DoingCommandRead.
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		DisableInterrupt(INTERRUPT_SINVAL_CATCHUP);
+		DisableInterrupt(INTERRUPT_ASYNC_NOTIFY);
+		DisableInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
 		 * slept.
 		 */
-		if (ConfigReloadPending)
-		{
-			ConfigReloadPending = false;
+		if (ConsumeInterrupt(INTERRUPT_CONFIG_RELOAD))
 			ProcessConfigFile(PGC_SIGHUP);
-		}
 
 		/*
 		 * (7) process the command.  But ignore it if we're skipping till
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index 87070235d11..6aa69805891 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/mcxtfuncs.c b/src/backend/utils/adt/mcxtfuncs.c
index c7f7b8bc2dd..188038c9f68 100644
--- a/src/backend/utils/adt/mcxtfuncs.c
+++ b/src/backend/utils/adt/mcxtfuncs.c
@@ -16,6 +16,7 @@
 #include "postgres.h"
 
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -266,7 +267,6 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 {
 	int			pid = PG_GETARG_INT32(0);
 	PGPROC	   *proc;
-	ProcNumber	procNumber = INVALID_PROC_NUMBER;
 
 	/*
 	 * See if the process with given pid is a backend or an auxiliary process.
@@ -295,14 +295,7 @@ pg_log_backend_memory_contexts(PG_FUNCTION_ARGS)
 		PG_RETURN_BOOL(false);
 	}
 
-	procNumber = GetNumberFromPGProc(proc);
-	if (SendProcSignal(pid, PROCSIG_LOG_MEMORY_CONTEXT, procNumber) < 0)
-	{
-		/* Again, just a warning to allow loops */
-		ereport(WARNING,
-				(errmsg("could not send signal to process %d: %m", pid)));
-		PG_RETURN_BOOL(false);
-	}
+	SendInterrupt(INTERRUPT_LOG_MEMORY_CONTEXT, GetNumberFromPGProc(proc));
 
 	PG_RETURN_BOOL(true);
 }
diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c
index 32a787d7df7..9f660363921 100644
--- a/src/backend/utils/adt/misc.c
+++ b/src/backend/utils/adt/misc.c
@@ -30,6 +30,7 @@
 #include "commands/tablespace.h"
 #include "common/keywords.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "parser/parse_type.h"
@@ -38,7 +39,6 @@
 #include "postmaster/syslogger.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -346,16 +346,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;
 
@@ -374,11 +374,10 @@ pg_sleep(PG_FUNCTION_ARGS)
 		else
 			break;
 
-		(void) WaitLatch(MyLatch,
-						 WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
-						 delay_ms,
-						 WAIT_EVENT_PG_SLEEP);
-		ResetLatch(MyLatch);
+		(void) WaitInterrupt(CheckForInterruptsMask,
+							 WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
+							 delay_ms,
+							 WAIT_EVENT_PG_SLEEP);
 	}
 
 	PG_RETURN_VOID();
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index 6f8cf29c910..b9d51e0e5f3 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -1737,10 +1737,10 @@ TimestampDifference(TimestampTz start_time, TimestampTz stop_time,
  * TimestampDifferenceMilliseconds -- convert the difference between two
  * 		timestamps into integer milliseconds
  *
- * This is typically used to calculate a wait timeout for WaitLatch()
+ * This is typically used to calculate a wait timeout for WaitInterrupt()
  * or a related function.  The choice of "long" as the result type
  * is to harmonize with that; furthermore, we clamp the result to at most
- * INT_MAX milliseconds, because that's all that WaitLatch() allows.
+ * INT_MAX milliseconds, because that's all that WaitInterrupt() allows.
  *
  * We expect start_time <= stop_time.  If not, we return zero,
  * since then we're already past the previously determined stop_time.
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 80b78f25267..a36c7c33249 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -537,7 +537,6 @@ errfinish(const char *filename, int lineno, const char *funcname)
 		 * should make life easier for most.)
 		 */
 		InterruptHoldoffCount = 0;
-		QueryCancelHoldoffCount = 0;
 
 		CritSectionCount = 0;	/* should be unnecessary, but... */
 
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index 36ad708b360..a87475319a3 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -29,20 +29,6 @@
 
 ProtocolVersion FrontendProtocol;
 
-volatile sig_atomic_t InterruptPending = false;
-volatile sig_atomic_t QueryCancelPending = false;
-volatile sig_atomic_t ProcDiePending = false;
-volatile sig_atomic_t CheckClientConnectionPending = false;
-volatile sig_atomic_t ClientConnectionLost = false;
-volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
-volatile sig_atomic_t TransactionTimeoutPending = false;
-volatile sig_atomic_t IdleSessionTimeoutPending = false;
-volatile sig_atomic_t ProcSignalBarrierPending = false;
-volatile sig_atomic_t LogMemoryContextPending = false;
-volatile sig_atomic_t IdleStatsUpdateTimeoutPending = false;
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 QueryCancelHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
 
 int			MyProcPid;
 pg_time_t	MyStartTime;
@@ -53,15 +39,6 @@ uint8		MyCancelKey[MAX_CANCEL_KEY_LENGTH];
 int			MyCancelKeyLength = 0;
 int			MyPMChildSlot;
 
-/*
- * MyLatch points to the latch that should be used for signal handling by the
- * current process. It will either point to a process local latch if the
- * current process does not have a PGPROC entry in that moment, or to
- * PGPROC->procLatch if it has. Thus it can always be used in signal handlers,
- * without checking for its existence.
- */
-struct Latch *MyLatch;
-
 /*
  * DataDir is the absolute path to the top level of the PGDATA directory tree.
  * Except during early startup, this is also the server's working directory;
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index fadf856d9bc..fdcee946405 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,18 +32,17 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/pg_shmem.h"
 #include "storage/pmsignal.h"
 #include "storage/proc.h"
@@ -66,8 +65,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
  *
@@ -126,10 +123,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
@@ -143,25 +139,12 @@ InitPostmasterChild(void)
 #endif
 
 	/*
-	 * Reset signals that are used by postmaster but not by child processes.
+	 * Set standard signal handlers suitable for most backend processes.
 	 *
-	 * Currently just SIGCHLD.  The handlers for other signals are overridden
-	 * later, depending on the child process type.
+	 * We don't unblock the signals yet, so that if the process needs special
+	 * signal handling, it can install custom handlers before unblocking.
 	 */
-	pqsignal(SIGCHLD, SIG_DFL); /* system() requires this to be SIG_DFL rather
-								 * than SIG_IGN on some platforms */
-
-	/*
-	 * Every postmaster child process is expected to respond promptly to
-	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
-	 * BlockSig and install a suitable signal handler.  (Client-facing
-	 * processes may choose to replace this default choice of handler with
-	 * quickdie().)  All other blockable signals remain blocked for now.
-	 */
-	pqsignal(SIGQUIT, SignalHandlerForCrashExit);
-
-	sigdelset(&BlockSig, SIGQUIT);
-	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
+	SetPostmasterChildSignalHandlers();
 
 	/*
 	 * It is up to the *Main() function to set signal handlers appropriate for
@@ -201,10 +184,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.
@@ -225,48 +207,6 @@ InitStandaloneProcess(const char *argv0)
 		get_pkglib_path(my_exec_path, pkglib_path);
 }
 
-void
-SwitchToSharedLatch(void)
-{
-	Assert(MyLatch == &LocalLatchData);
-	Assert(MyProc != NULL);
-
-	MyLatch = &MyProc->procLatch;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	/*
-	 * Set the shared latch as the local one might have been set. This
-	 * shouldn't normally be necessary as code is supposed to check the
-	 * condition before waiting for the latch, but a bit care can't hurt.
-	 */
-	SetLatch(MyLatch);
-}
-
-void
-InitProcessLocalLatch(void)
-{
-	MyLatch = &LocalLatchData;
-	InitLatch(MyLatch);
-}
-
-void
-SwitchBackToLocalLatch(void)
-{
-	Assert(MyLatch != &LocalLatchData);
-	Assert(MyProc != NULL && MyLatch == &MyProc->procLatch);
-
-	MyLatch = &LocalLatchData;
-
-	if (FeBeWaitSet)
-		ModifyWaitEvent(FeBeWaitSet, FeBeWaitSetLatchPos, WL_LATCH_SET,
-						MyLatch);
-
-	SetLatch(MyLatch);
-}
-
 /*
  * Return a human-readable string representation of a BackendType.
  *
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index b59e08605cc..4f1aa94a661 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -33,6 +33,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_tablespace.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/libpq-be.h"
 #include "mb/pg_wchar.h"
@@ -1371,20 +1372,19 @@ ShutdownPostgres(int code, Datum arg)
 static void
 StatementTimeoutHandler(void)
 {
-	int			sig = SIGINT;
-
 	/*
 	 * During authentication the timeout is used to deal with
 	 * authentication_timeout - we want to quit in response to such timeouts.
 	 */
 	if (ClientAuthInProgress)
-		sig = SIGTERM;
+		RaiseInterrupt(INTERRUPT_TERMINATE);
+	else
+		RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, sig);
-#endif
-	kill(MyProcPid, sig);
+	/*
+	 * FIXME: we used to signal the whole process group. Is that important, if
+	 * we're e.g. executing archive_command ?
+	 */
 }
 
 /*
@@ -1393,51 +1393,37 @@ StatementTimeoutHandler(void)
 static void
 LockTimeoutHandler(void)
 {
-#ifdef HAVE_SETSID
-	/* try to signal whole process group */
-	kill(-MyProcPid, SIGINT);
-#endif
-	kill(MyProcPid, SIGINT);
+	RaiseInterrupt(INTERRUPT_QUERY_CANCEL);
 }
 
 static void
 TransactionTimeoutHandler(void)
 {
-	TransactionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_TRANSACTION_TIMEOUT);
 }
 
 static void
 IdleInTransactionSessionTimeoutHandler(void)
 {
-	IdleInTransactionSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT);
 }
 
 static void
 IdleSessionTimeoutHandler(void)
 {
-	IdleSessionTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_SESSION_TIMEOUT);
 }
 
 static void
 IdleStatsUpdateTimeoutHandler(void)
 {
-	IdleStatsUpdateTimeoutPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_IDLE_STATS_TIMEOUT);
 }
 
 static void
 ClientCheckTimeoutHandler(void)
 {
-	CheckClientConnectionPending = true;
-	InterruptPending = true;
-	SetLatch(MyLatch);
+	RaiseInterrupt(INTERRUPT_CLIENT_CHECK_TIMEOUT);
 }
 
 /*
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..1f3ad3dbf51 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -17,7 +17,6 @@
 #include <sys/time.h>
 
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "utils/timeout.h"
 #include "utils/timestamp.h"
 
@@ -370,12 +369,6 @@ handle_sig_alarm(SIGNAL_ARGS)
 	 */
 	HOLD_INTERRUPTS();
 
-	/*
-	 * SIGALRM is always cause for waking anything waiting on the process
-	 * latch.
-	 */
-	SetLatch(MyLatch);
-
 	/*
 	 * Always reset signal_pending, even if !alarm_enabled, since indeed no
 	 * signal is now pending.
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 073bdb35d2a..996cae05b27 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -1311,36 +1311,16 @@ MemoryContextAllocExtended(MemoryContext context, Size size, int flags)
 	return ret;
 }
 
-/*
- * HandleLogMemoryContextInterrupt
- *		Handle receipt of an interrupt indicating logging of memory
- *		contexts.
- *
- * All the actual work is deferred to ProcessLogMemoryContextInterrupt(),
- * because we cannot safely emit a log message inside the signal handler.
- */
-void
-HandleLogMemoryContextInterrupt(void)
-{
-	InterruptPending = true;
-	LogMemoryContextPending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * ProcessLogMemoryContextInterrupt
  * 		Perform logging of memory contexts of this backend process.
  *
- * Any backend that participates in ProcSignal signaling must arrange
- * to call this function if we see LogMemoryContextPending set.
- * It is called from CHECK_FOR_INTERRUPTS(), which is enough because
- * the target process for logging of memory contexts is a backend.
+ * This is the handler for INTERRUPT_LOG_MEMORY_CONTEXT, normally called from
+ * CHECK_FOR_INTERRUPTS().
  */
 void
 ProcessLogMemoryContextInterrupt(void)
 {
-	LogMemoryContextPending = false;
-
 	/*
 	 * Exit immediately if memory context logging is already in progress. This
 	 * prevents recursive calls, which could occur if logging is requested
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/include/Makefile b/src/include/Makefile
index ac673f4cf17..c474d6fbe3c 100644
--- a/src/include/Makefile
+++ b/src/include/Makefile
@@ -28,6 +28,7 @@ SUBDIRS = \
 	executor \
 	fe_utils \
 	foreign \
+	ipc \
 	jit \
 	lib \
 	libpq \
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index 60f857675e0..6d57c310239 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -55,7 +55,6 @@ typedef struct ParallelWorkerContext
 	shm_toc    *toc;
 } ParallelWorkerContext;
 
-extern PGDLLIMPORT volatile sig_atomic_t ParallelMessagePending;
 extern PGDLLIMPORT int ParallelWorkerNumber;
 extern PGDLLIMPORT bool InitializingParallelWorker;
 
@@ -72,7 +71,6 @@ extern void WaitForParallelWorkersToFinish(ParallelContext *pcxt);
 extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
-extern void HandleParallelMessageInterrupt(void);
 extern void ProcessParallelMessages(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
diff --git a/src/include/access/xlogrecovery.h b/src/include/access/xlogrecovery.h
index 2842106b285..7564d23cf7b 100644
--- a/src/include/access/xlogrecovery.h
+++ b/src/include/access/xlogrecovery.h
@@ -15,7 +15,6 @@
 #include "catalog/pg_control.h"
 #include "lib/stringinfo.h"
 #include "storage/condition_variable.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 
 /*
@@ -77,23 +76,6 @@ typedef struct XLogRecoveryCtlData
 	 */
 	bool		SharedPromoteIsTriggered;
 
-	/*
-	 * recoveryWakeupLatch is used to wake up the startup process to continue
-	 * WAL replay, if it is waiting for WAL to arrive or promotion to be
-	 * requested.
-	 *
-	 * Note that the startup process also uses another latch, its procLatch,
-	 * to wait for recovery conflict. If we get rid of recoveryWakeupLatch for
-	 * signaling the startup process in favor of using its procLatch, which
-	 * comports better with possible generic signal handlers using that latch.
-	 * But we should not do that because the startup process doesn't assume
-	 * that it's waken up by walreceiver process or SIGHUP signal handler
-	 * while it's waiting for recovery conflict. The separate latches,
-	 * recoveryWakeupLatch and procLatch, should be used for inter-process
-	 * communication for WAL replay and recovery conflict, respectively.
-	 */
-	Latch		recoveryWakeupLatch;
-
 	/*
 	 * Last record successfully replayed.
 	 */
diff --git a/src/include/commands/async.h b/src/include/commands/async.h
index 3baae7cb8dc..acd9afbcc4b 100644
--- a/src/include/commands/async.h
+++ b/src/include/commands/async.h
@@ -13,11 +13,8 @@
 #ifndef ASYNC_H
 #define ASYNC_H
 
-#include <signal.h>
-
 extern PGDLLIMPORT bool Trace_notify;
 extern PGDLLIMPORT int max_notify_queue_pages;
-extern PGDLLIMPORT volatile sig_atomic_t notifyInterruptPending;
 
 extern Size AsyncShmemSize(void);
 extern void AsyncShmemInit(void);
@@ -40,9 +37,6 @@ extern void AtSubCommit_Notify(void);
 extern void AtSubAbort_Notify(void);
 extern void AtPrepare_Notify(void);
 
-/* signal handler for inbound notifies (PROCSIG_NOTIFY_INTERRUPT) */
-extern void HandleNotifyInterrupt(void);
-
 /* process interrupts */
 extern void ProcessNotifyInterrupt(bool flush);
 
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..239d58f7597
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,370 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ */
+#include "storage/waiteventset.h"
+
+
+/*
+ * Flags in the pending interrupts bitmask. Each value is a different bit, so that
+ * these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 63
+
+/*
+ * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
+ * waiting for an interrupt.  If set, the backend needs to be woken up when a
+ * bit in the pending interrupts mask is set.  It's used internally by the
+ * interrupt machinery, and cannot be used directly in the public functions.
+ * It's named differently to distinguish it from the actual interrupt flags.
+ */
+#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
+
+extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
+
+/*
+ * Test an interrupt flag (or flags).
+ */
+static inline bool
+InterruptPending(InterruptMask interruptMask)
+{
+	/*
+	 * Note that there is no memory barrier here. This is used in
+	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
+	 *
+	 * That means that if the interrupt is concurrently set by another
+	 * process, we might miss it. That should be OK, because the next
+	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
+	 * We will see the updated value before sleeping.
+	 */
+	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+}
+
+/*
+ * Clear an interrupt flag (or flags).
+ */
+static inline void
+ClearInterrupt(InterruptMask interruptMask)
+{
+	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+}
+
+/*
+ * Test and clear an interrupt flag (or flags).
+ */
+static inline bool
+ConsumeInterrupt(InterruptMask interruptMask)
+{
+	if (likely(!InterruptPending(interruptMask)))
+		return false;
+
+	ClearInterrupt(interruptMask);
+	return true;
+}
+
+extern void RaiseInterrupt(InterruptMask interruptMask);
+extern void SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno);
+extern int	WaitInterrupt(InterruptMask interruptMask, int wakeEvents, long timeout,
+						  uint32 wait_event_info);
+extern int	WaitInterruptOrSocket(InterruptMask interruptMask, int wakeEvents, pgsocket sock,
+								  long timeout, uint32 wait_event_info);
+extern void SwitchToLocalInterrupts(void);
+extern void SwitchToSharedInterrupts(void);
+extern void InitializeInterruptWaitSet(void);
+
+typedef void (*pg_interrupt_handler_t) (void);
+extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
+
+extern void EnableInterrupt(InterruptMask interruptMask);
+extern void DisableInterrupt(InterruptMask interruptMask);
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+/*****************************************************************************
+ *	  CHECK_FOR_INTERRUPTS() and friends
+ *****************************************************************************/
+
+
+extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
+extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
+
+extern void ProcessInterrupts(void);
+
+/* Test whether an interrupt is pending */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(InterruptPending(mask)))
+#else
+#define INTERRUPTS_PENDING_CONDITION(mask) \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0,	\
+	 unlikely(InterruptPending(mask)))
+#endif
+
+/*
+ * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
+ *
+ * (The interrupt handler may re-raise the interrupt, though)
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
+	(((mask) & CheckForInterruptsMask) == (mask))
+
+/* Service interrupt, if one is pending and it's safe to service it now */
+#define CHECK_FOR_INTERRUPTS()					\
+do { \
+	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+		ProcessInterrupts();											\
+} while(0)
+
+
+/*****************************************************************************
+ *	  Critical section and interrupt holdoff mechanism
+ *****************************************************************************/
+
+/* these are marked volatile because they are examined by signal handlers: */
+/*
+ * XXX: is that still true? Should we use local vars to avoid repeated access
+ * e.g. inside RESUME_INTERRUPTS() ?
+ */
+extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT volatile uint32 CritSectionCount;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+	CheckForInterruptsMask = (InterruptMask) 0;
+}
+
+static inline void
+RESUME_INTERRUPTS(void)
+{
+	Assert(CheckForInterruptsMask == 0);
+	Assert(InterruptHoldoffCount > 0);
+	InterruptHoldoffCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+	CheckForInterruptsMask = 0;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
+		CheckForInterruptsMask = EnabledInterruptsMask;
+	else
+		Assert(CheckForInterruptsMask == 0);
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 82%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..cbe9143cfdf 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -21,12 +21,10 @@
 
 #include <signal.h>
 
-extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
-extern PGDLLIMPORT volatile sig_atomic_t ShutdownRequestPending;
-
-extern void ProcessMainLoopInterrupts(void);
+extern void SetPostmasterChildSignalHandlers(void);
 extern void SignalHandlerForConfigReload(SIGNAL_ARGS);
 extern void SignalHandlerForCrashExit(SIGNAL_ARGS);
 extern void SignalHandlerForShutdownRequest(SIGNAL_ARGS);
+extern void SignalHandlerForQueryCancel(SIGNAL_ARGS);
 
 #endif
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index 85d8b63f019..53d80f40d10 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,10 +30,9 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
-#include "miscadmin.h"
 #include "storage/fd.h"
-#include "storage/latch.h"
 #include "utils/timestamp.h"
 #include "utils/wait_event.h"
 
@@ -167,8 +166,8 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 		return;
 
 	/*
-	 * WaitLatchOrSocket() can conceivably fail, handle that case here instead
-	 * of requiring all callers to do so.
+	 * WaitInterruptOrSocket() can conceivably fail, handle that case here
+	 * instead of requiring all callers to do so.
 	 */
 	PG_TRY();
 	{
@@ -199,18 +198,15 @@ libpqsrv_connect_internal(PGconn *conn, uint32 wait_event_info)
 			else
 				io_flag = WL_SOCKET_WRITEABLE;
 
-			rc = WaitLatchOrSocket(MyLatch,
-								   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET | io_flag,
-								   PQsocket(conn),
-								   0,
-								   wait_event_info);
+			rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+									   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT | io_flag,
+									   PQsocket(conn),
+									   0,
+									   wait_event_info);
 
 			/* Interrupted? */
-			if (rc & WL_LATCH_SET)
-			{
-				ResetLatch(MyLatch);
+			if (rc & WL_INTERRUPT)
 				CHECK_FOR_INTERRUPTS();
-			}
 
 			/* If socket is ready, advance the libpq state machine */
 			if (rc & io_flag)
@@ -320,19 +316,16 @@ libpqsrv_get_result(PGconn *conn, uint32 wait_event_info)
 	{
 		int			rc;
 
-		rc = WaitLatchOrSocket(MyLatch,
-							   WL_EXIT_ON_PM_DEATH | WL_LATCH_SET |
-							   WL_SOCKET_READABLE,
-							   PQsocket(conn),
-							   0,
-							   wait_event_info);
+		rc = WaitInterruptOrSocket(CheckForInterruptsMask,
+								   WL_EXIT_ON_PM_DEATH | WL_INTERRUPT |
+								   WL_SOCKET_READABLE,
+								   PQsocket(conn),
+								   0,
+								   wait_event_info);
 
 		/* Interrupted? */
-		if (rc & WL_LATCH_SET)
-		{
-			ResetLatch(MyLatch);
+		if (rc & WL_INTERRUPT)
 			CHECK_FOR_INTERRUPTS();
-		}
 
 		/* Consume whatever data is available from the socket */
 		if (PQconsumeInput(conn) == 0)
@@ -386,7 +379,7 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			PostgresPollingStatusType pollres;
 			TimestampTz now;
 			long		cur_timeout;
-			int			waitEvents = WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
+			int			waitEvents = WL_INTERRUPT | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH;
 
 			pollres = PQcancelPoll(cancel_conn);
 			if (pollres == PGRES_POLLING_OK)
@@ -415,10 +408,9 @@ libpqsrv_cancel(PGconn *conn, TimestampTz endtime)
 			}
 
 			/* Sleep until there's something to do */
-			WaitLatchOrSocket(MyLatch, waitEvents, PQcancelSocket(cancel_conn),
-							  cur_timeout, PG_WAIT_CLIENT);
-
-			ResetLatch(MyLatch);
+			WaitInterruptOrSocket(CheckForInterruptsMask, waitEvents,
+								  PQcancelSocket(cancel_conn),
+								  cur_timeout, PG_WAIT_CLIENT);
 
 			CHECK_FOR_INTERRUPTS();
 		}
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 790724b6a0b..b809073d48c 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -64,7 +64,7 @@ extern const PGDLLIMPORT PQcommMethods *PqCommMethods;
 extern PGDLLIMPORT WaitEventSet *FeBeWaitSet;
 
 #define FeBeWaitSetSocketPos 0
-#define FeBeWaitSetLatchPos 1
+#define FeBeWaitSetInterruptPos 1
 #define FeBeWaitSetNEvents 3
 
 extern int	ListenServerPort(int family, const char *hostName,
diff --git a/src/include/libpq/pqmq.h b/src/include/libpq/pqmq.h
index 36780c0816e..ea96ae9545c 100644
--- a/src/include/libpq/pqmq.h
+++ b/src/include/libpq/pqmq.h
@@ -18,7 +18,7 @@
 #include "storage/shm_mq.h"
 
 extern void pq_redirect_to_shm_mq(dsm_segment *seg, shm_mq_handle *mqh);
-extern void pq_set_parallel_leader(pid_t pid, ProcNumber procNumber);
+extern void pq_set_parallel_leader(ProcNumber procNumber);
 
 extern void pq_parse_errornotice(StringInfo msg, ErrorData *edata);
 
diff --git a/src/include/meson.build b/src/include/meson.build
index 7d734d92dab..da8129355cc 100644
--- a/src/include/meson.build
+++ b/src/include/meson.build
@@ -135,6 +135,7 @@ header_subdirs = [
   'executor',
   'fe_utils',
   'foreign',
+  'ipc',
   'jit',
   'lib',
   'libpq',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index f16f35659b9..476e60a4d06 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -28,132 +28,15 @@
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
-
-#define InvalidPid				(-1)
-
-
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t TransactionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleSessionTimeoutPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
-extern PGDLLIMPORT volatile sig_atomic_t LogMemoryContextPending;
-extern PGDLLIMPORT volatile sig_atomic_t IdleStatsUpdateTimeoutPending;
-
-extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
-extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
+/*
+ * XXX: many files include miscadmin.h for CHECK_FOR_INTERRUPTS(). Keep them
+ * working without changes
+ */
+#ifndef FRONTEND
+#include "ipc/interrupt.h"
 #endif
 
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
+#define InvalidPid				(-1)
 
 
 /*****************************************************************************
@@ -191,7 +74,6 @@ extern PGDLLIMPORT int MyProcPid;
 extern PGDLLIMPORT pg_time_t MyStartTime;
 extern PGDLLIMPORT TimestampTz MyStartTimestamp;
 extern PGDLLIMPORT struct Port *MyProcPort;
-extern PGDLLIMPORT struct Latch *MyLatch;
 extern PGDLLIMPORT uint8 MyCancelKey[];
 extern PGDLLIMPORT int MyCancelKeyLength;
 extern PGDLLIMPORT int MyPMChildSlot;
@@ -324,9 +206,6 @@ extern PGDLLIMPORT char *DatabasePath;
 /* now in utils/init/miscinit.c */
 extern void InitPostmasterChild(void);
 extern void InitStandaloneProcess(const char *argv0);
-extern void InitProcessLocalLatch(void);
-extern void SwitchToSharedLatch(void);
-extern void SwitchBackToLocalLatch(void);
 
 /*
  * MyBackendType indicates what kind of a backend this is.
diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h
index 7418e64e19b..53ae10722de 100644
--- a/src/include/postmaster/bgworker.h
+++ b/src/include/postmaster/bgworker.h
@@ -66,6 +66,14 @@
  */
 #define BGWORKER_INTERRUPTIBLE			0x0004
 
+/*
+ * Dynamic workers created with shared memory access usually send an interrupt
+ * to the creating backend when they start and stop, allowing
+ * WaitForBackgroundWorker{Startup,Shutdown}() to work.  Such notifications
+ * can be suppressed with this flag.
+ */
+#define BGWORKER_NO_NOTIFY							0x0008
+
 /*
  * This class is used internally for parallel queries, to keep track of the
  * number of active parallel workers and make sure we never launch more than
@@ -104,7 +112,7 @@ typedef struct BackgroundWorker
 	char		bgw_function_name[BGW_MAXLEN];
 	Datum		bgw_main_arg;
 	char		bgw_extra[BGW_EXTRALEN];
-	pid_t		bgw_notify_pid; /* SIGUSR1 this backend on start/stop */
+	pid_t		bgw_notify_pid; /* not used */
 } BackgroundWorker;
 
 typedef enum BgwHandleStatus
diff --git a/src/include/postmaster/bgworker_internals.h b/src/include/postmaster/bgworker_internals.h
index b789caf4034..8dca9f84339 100644
--- a/src/include/postmaster/bgworker_internals.h
+++ b/src/include/postmaster/bgworker_internals.h
@@ -15,6 +15,7 @@
 #include "datatype/timestamp.h"
 #include "lib/ilist.h"
 #include "postmaster/bgworker.h"
+#include "storage/procnumber.h"
 
 /* GUC options */
 
@@ -47,9 +48,10 @@ extern void BackgroundWorkerStateChange(bool allow_new_workers);
 extern void ForgetBackgroundWorker(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerPID(RegisteredBgWorker *rw);
 extern void ReportBackgroundWorkerExit(RegisteredBgWorker *rw);
-extern void BackgroundWorkerStopNotifications(pid_t pid);
+extern void BackgroundWorkerStopNotifications(int pmchild);
 extern void ForgetUnstartedBackgroundWorkers(void);
 extern void ResetBackgroundWorkerCrashTimes(void);
+extern ProcNumber GetNotifyProcNumberForRegisteredWorker(RegisteredBgWorker *rw);
 
 /* Entry point for background worker processes */
 pg_noreturn extern void BackgroundWorkerMain(const void *startup_data, size_t startup_data_len);
diff --git a/src/include/postmaster/startup.h b/src/include/postmaster/startup.h
index 4d1e12131d3..dfb2b0b65cf 100644
--- a/src/include/postmaster/startup.h
+++ b/src/include/postmaster/startup.h
@@ -25,12 +25,10 @@
 
 extern PGDLLIMPORT int log_startup_progress_interval;
 
-extern void ProcessStartupProcInterrupts(void);
+extern void StartupProcCheckPostmasterDeath(void);
 pg_noreturn extern void StartupProcessMain(const void *startup_data, size_t startup_data_len);
 extern void PreRestoreCommand(void);
 extern void PostRestoreCommand(void);
-extern bool IsPromoteSignaled(void);
-extern void ResetPromoteSignaled(void);
 
 extern void enable_startup_progress_timeout(void);
 extern void disable_startup_progress_timeout(void);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..382269001cf 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -12,10 +12,6 @@
 #ifndef LOGICALWORKER_H
 #define LOGICALWORKER_H
 
-#include <signal.h>
-
-extern PGDLLIMPORT volatile sig_atomic_t ParallelApplyMessagePending;
-
 extern void ApplyWorkerMain(Datum main_arg);
 extern void ParallelApplyWorkerMain(Datum main_arg);
 extern void TableSyncWorkerMain(Datum main_arg);
@@ -24,7 +20,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/replication/walsender.h b/src/include/replication/walsender.h
index a4df3b8e0ae..69b95bb27ec 100644
--- a/src/include/replication/walsender.h
+++ b/src/include/replication/walsender.h
@@ -46,7 +46,6 @@ extern void WalSndShmemInit(void);
 extern void WalSndWakeup(bool physical, bool logical);
 extern void WalSndInitStopping(void);
 extern void WalSndWaitStopping(void);
-extern void HandleWalSndInitStopping(void);
 extern void WalSndRqstFileReload(void);
 
 /*
diff --git a/src/include/replication/walsender_private.h b/src/include/replication/walsender_private.h
index 5de674d5410..c18b64359df 100644
--- a/src/include/replication/walsender_private.h
+++ b/src/include/replication/walsender_private.h
@@ -40,6 +40,8 @@ typedef enum WalSndState
  */
 typedef struct WalSnd
 {
+	ProcNumber	pgprocno;		/* this walsender's ProcNumber, or invalid if
+								 * not active */
 	pid_t		pid;			/* this walsender's PID, or 0 if not active */
 
 	WalSndState state;			/* this walsender's state */
diff --git a/src/include/replication/worker_internal.h b/src/include/replication/worker_internal.h
index 4ecbdcfadac..d368bc60536 100644
--- a/src/include/replication/worker_internal.h
+++ b/src/include/replication/worker_internal.h
@@ -79,9 +79,10 @@ typedef struct LogicalRepWorker
 	FileSet    *stream_fileset;
 
 	/*
-	 * PID of leader apply worker if this slot is used for a parallel apply
-	 * worker, InvalidPid otherwise.
+	 * Leader apply worker if this slot is used for a parallel apply worker,
+	 * INVALID_PROC_NUMBER/InvalidPid otherwise.
 	 */
+	ProcNumber	leader_pgprocno;
 	pid_t		leader_pid;
 
 	/* Indicates whether apply can be performed in parallel. */
diff --git a/src/include/storage/latch.h b/src/include/storage/latch.h
deleted file mode 100644
index fbdadc86959..00000000000
--- a/src/include/storage/latch.h
+++ /dev/null
@@ -1,140 +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 */
-
-/*
- * 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 4a1fafb9038..1ca4c70715c 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -18,7 +18,6 @@
 #include "access/xlogdefs.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
-#include "storage/latch.h"
 #include "storage/lock.h"
 #include "storage/pg_sema.h"
 #include "storage/proclist_types.h"
@@ -251,12 +250,18 @@ typedef struct PGPROC
 	 * Inter-process signaling
 	 ************************************************************************/
 
-	Latch		procLatch;		/* generic latch for process */
-
 	PGSemaphore sem;			/* ONE semaphore to sleep on */
 
 	int			delayChkptFlags;	/* for DELAY_CHKPT_* flags */
 
+	/* Bit mask of pending interrupts, waiting to be processed */
+	pg_atomic_uint64 pendingInterrupts;
+
+#ifdef WIN32
+	/* Event handle to wake up the process when sending an interrupt */
+	HANDLE          interruptWakeupEvent;
+#endif
+
 	/*
 	 * While in hot standby mode, shows that a conflict signal has been sent
 	 * for the current transaction. Set/cleared while holding ProcArrayLock,
@@ -494,6 +499,8 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 avLauncherProc;
 	pg_atomic_uint32 walwriterProc;
 	pg_atomic_uint32 checkpointerProc;
+	pg_atomic_uint32 walreceiverProc;
+	pg_atomic_uint32 startupProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
@@ -577,9 +584,6 @@ extern void GetLockHoldersAndWaiters(LOCALLOCK *locallock,
 									 StringInfo lock_waiters_sbuf,
 									 int *lockHoldersNum);
 
-extern void ProcWaitForSignal(uint32 wait_event_info);
-extern void ProcSendSignal(ProcNumber procNumber);
-
 extern PGPROC *AuxiliaryPidGetProc(int pid);
 
 extern void BecomeLockGroupLeader(void);
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index c5ab1574fe3..b7201d1a917 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -78,7 +78,7 @@ extern VirtualTransactionId *GetCurrentVirtualXIDs(TransactionId limitXmin,
 												   int *nvxids);
 extern VirtualTransactionId *GetConflictingVirtualXIDs(TransactionId limitXmin, Oid dbOid);
 
-extern bool SignalRecoveryConflict(PGPROC *proc, pid_t pid, RecoveryConflictReason reason);
+extern bool SignalRecoveryConflict(PGPROC *proc, RecoveryConflictReason reason);
 extern bool SignalRecoveryConflictWithVirtualXID(VirtualTransactionId vxid, RecoveryConflictReason reason);
 extern void SignalRecoveryConflictWithDatabase(Oid databaseid, RecoveryConflictReason reason);
 
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index 348fba53a93..18b28b7b704 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -14,35 +14,6 @@
 #ifndef PROCSIGNAL_H
 #define PROCSIGNAL_H
 
-#include "storage/procnumber.h"
-
-
-/*
- * Reasons for signaling a Postgres child process (a backend or an auxiliary
- * process, like checkpointer).  We can cope with concurrent signals for different
- * reasons.  However, if the same reason is signaled multiple times in quick
- * succession, the process is likely to observe only one notification of it.
- * This is okay for the present uses.
- *
- * Also, because of race conditions, it's important that all the signals be
- * defined so that no harm is done if a process mistakenly receives one.
- */
-typedef enum
-{
-	PROCSIG_CATCHUP_INTERRUPT,	/* sinval catchup interrupt */
-	PROCSIG_NOTIFY_INTERRUPT,	/* listen/notify interrupt */
-	PROCSIG_PARALLEL_MESSAGE,	/* message from cooperating parallel backend */
-	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
-	PROCSIG_BARRIER,			/* global barrier interrupt  */
-	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
-	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
-								 * PGPROC->pendingRecoveryConflicts for the
-								 * reason */
-} ProcSignalReason;
-
-#define NUM_PROCSIGNALS (PROCSIG_RECOVERY_CONFLICT + 1)
-
 typedef enum
 {
 	PROCSIGNAL_BARRIER_SMGRRELEASE, /* ask smgr to close files */
@@ -67,16 +38,12 @@ extern Size ProcSignalShmemSize(void);
 extern void ProcSignalShmemInit(void);
 
 extern void ProcSignalInit(const uint8 *cancel_key, int cancel_key_len);
-extern int	SendProcSignal(pid_t pid, ProcSignalReason reason,
-						   ProcNumber procNumber);
 extern void SendCancelRequest(int backendPID, const uint8 *cancel_key, int cancel_key_len);
 
 extern uint64 EmitProcSignalBarrier(ProcSignalBarrierType type);
 extern void WaitForProcSignalBarrier(uint64 generation);
 extern void ProcessProcSignalBarrier(void);
 
-extern void procsignal_sigusr1_handler(SIGNAL_ARGS);
-
 /* ProcSignalHeader is an opaque struct, details known only within procsignal.c */
 typedef struct ProcSignalHeader ProcSignalHeader;
 
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index 4d33fdcabe1..6cecbfd9cf8 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -137,16 +137,11 @@ typedef union
 /* Counter of messages processed; don't worry about overflow. */
 extern PGDLLIMPORT uint64 SharedInvalidMessageCounter;
 
-extern PGDLLIMPORT volatile sig_atomic_t catchupInterruptPending;
-
 extern void SendSharedInvalidMessages(const SharedInvalidationMessage *msgs,
 									  int n);
 extern void ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *msg),
 										 void (*resetFunction) (void));
 
-/* signal handler for catchup events (PROCSIG_CATCHUP_INTERRUPT) */
-extern void HandleCatchupInterrupt(void);
-
 /*
  * enable/disable processing of catchup events directly from signal handler.
  * The enable routine first performs processing of any catchup events that
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..a7f974c5ba2 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -3,16 +3,15 @@
  * waiteventset.h
  *		ppoll() / pselect() like interface for waiting for events
  *
- * WaitEventSets allow to wait for latches being set and additional events -
+ * WaitEventSets allow to wait for interrupts being set and additional events -
  * postmaster dying and socket readiness of several sockets currently - at the
  * same time.  On many platforms using a long lived event set is more
- * efficient than using WaitLatch or WaitLatchOrSocket.
+ * efficient than using WaitInterrupt or WaitInterruptOrSocket.
  *
- * WaitEventSetWait includes a provision for timeouts (which should be avoided
+ * WaitInterrupt includes a provision for timeouts (which should be avoided
  * when possible, as they incur extra overhead) and a provision for postmaster
  * child processes to wake up immediately on postmaster death.  See
- * storage/ipc/waiteventset.c for detailed specifications for the exported
- * functions.
+ * ipc/interrupt.c for detailed specifications for the exported functions.
  *
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
@@ -25,13 +24,17 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
+#include "storage/procnumber.h"
+
+/* defined here to avoid circular dependency to interrupt.h */
+typedef uint64 InterruptMask;
+
 
 /*
- * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
- * WaitEventSetWait().
+ * Bitmasks for events that may wake-up WaitInterrupt(),
+ * WaitInterruptOrSocket(), or WaitEventSetWait().
  */
-#define WL_LATCH_SET		 (1 << 0)
+#define WL_INTERRUPT		 (1 << 0)
 #define WL_SOCKET_READABLE	 (1 << 1)
 #define WL_SOCKET_WRITEABLE  (1 << 2)
 #define WL_TIMEOUT			 (1 << 3)	/* not for WaitEventSetWait() */
@@ -70,29 +73,33 @@ typedef struct WaitEvent
 /* forward declarations to avoid exposing waiteventset.c implementation details */
 typedef struct WaitEventSet WaitEventSet;
 
-struct Latch;
+struct PGPROC;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
+#ifdef WIN32
+extern HANDLE CreateSharedWakeupHandle(void);
+#endif
+
+struct ResourceOwnerData;
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+extern WaitEventSet *CreateWaitEventSet(struct ResourceOwnerData *resowner, int nevents);
 extern void FreeWaitEventSet(WaitEventSet *set);
 extern void FreeWaitEventSetAfterFork(WaitEventSet *set);
 extern int	AddWaitEventToSet(WaitEventSet *set, uint32 events, pgsocket fd,
-							  struct Latch *latch, void *user_data);
+							  uint64 interruptMask, void *user_data);
 extern void ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events,
-							struct Latch *latch);
+							uint64 interruptMask);
 extern int	WaitEventSetWait(WaitEventSet *set, long timeout,
 							 WaitEvent *occurred_events, int nevents,
 							 uint32 wait_event_info);
 extern int	GetNumRegisteredWaitEvents(WaitEventSet *set);
 extern bool WaitEventSetCanReportClosed(void);
 
-#ifndef WIN32
+/* low level functions used to implement SendInterrupt */
 extern void WakeupMyProc(void);
-extern void WakeupOtherProc(int pid);
-#endif
+extern void WakeupOtherProc(struct PGPROC *proc);
 
 #endif							/* WAITEVENTSET_H */
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..77ab950eae2 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
@@ -70,13 +69,7 @@ extern List *pg_plan_queries(List *querytrees, const char *query_string,
 							 int cursorOptions,
 							 ParamListInfo boundParams);
 
-extern void die(SIGNAL_ARGS);
-pg_noreturn extern void quickdie(SIGNAL_ARGS);
-extern void StatementCancelHandler(SIGNAL_ARGS);
 pg_noreturn extern void FloatExceptionHandler(SIGNAL_ARGS);
-extern void HandleRecoveryConflictInterrupt(void);
-extern void ProcessClientReadInterrupt(bool blocked);
-extern void ProcessClientWriteInterrupt(bool blocked);
 
 extern void process_postgres_switches(int argc, char *argv[],
 									  GucContext ctx, const char **dbname);
diff --git a/src/include/utils/memutils.h b/src/include/utils/memutils.h
index 11ab1717a16..5d47643eb77 100644
--- a/src/include/utils/memutils.h
+++ b/src/include/utils/memutils.h
@@ -101,7 +101,6 @@ extern void MemoryContextCheck(MemoryContext context);
 #define MemoryContextCopyAndSetIdentifier(cxt, id) \
 	MemoryContextSetIdentifier(cxt, MemoryContextStrdup(cxt, id))
 
-extern void HandleLogMemoryContextInterrupt(void);
 extern void ProcessLogMemoryContextInterrupt(void);
 
 /*
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 9b8fbae00ed..2cf809cd7c2 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/port/pgsleep.c b/src/port/pgsleep.c
index 4c31e503eb1..0bd50bdb23f 100644
--- a/src/port/pgsleep.c
+++ b/src/port/pgsleep.c
@@ -32,10 +32,10 @@
  *
  * CAUTION: It's not a good idea to use long sleeps in the backend.  They will
  * silently return early if a signal is caught, but that doesn't include
- * latches being set on most OSes, and even signal handlers that set MyLatch
- * might happen to run before the sleep begins, allowing the full delay.
- * Better practice is to use WaitLatch() with a timeout, so that backends
- * respond to latches and signals promptly.
+ * interrupts being received on most OSes, and even signal handlers that raise
+ * an interrupt might happen to run before the sleep begins, allowing the full
+ * delay.  Better practice is to use WaitInterrupt() with a timeout, so that
+ * backends respond to interrupts and signals promptly.
  */
 void
 pg_usleep(long microsec)
diff --git a/src/test/isolation/isolationtester.c b/src/test/isolation/isolationtester.c
index 440c875b8ac..d568e90c064 100644
--- a/src/test/isolation/isolationtester.c
+++ b/src/test/isolation/isolationtester.c
@@ -762,7 +762,7 @@ try_complete_steps(TestSpec *testspec, PermutationStep **waiting,
 	{
 		int			w = 0;
 
-		/* Reset latch; we only care about notices received within loop. */
+		/* Reset flag; we only care about notices received within loop. */
 		any_new_notice = false;
 
 		/* Likewise, these variables reset for each retry. */
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 36bb255922c..5c37500225d 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
@@ -134,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;
@@ -223,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)
@@ -265,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;
@@ -287,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 ce97e6e1aa4..02265ff0f30 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/proc.h"
 #include "varatt.h"
@@ -171,6 +171,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
@@ -235,13 +237,13 @@ test_shm_mq_pipelined(PG_FUNCTION_ARGS)
 
 			/*
 			 * If we made no progress, wait for one of the other processes to
-			 * which we are connected to set our latch, indicating that they
-			 * have read or written data and therefore there may now be work
-			 * for us to do.
+			 * which we are connected to send us the INTERRUPT_WAIT_WAKEUP
+			 * interrupt, indicating that they have read or written data and
+			 * therefore there may now be work for us to do.
 			 */
-			(void) WaitLatch(MyLatch, WL_LATCH_SET | WL_EXIT_ON_PM_DEATH, 0,
-							 we_message_queue);
-			ResetLatch(MyLatch);
+			(void) WaitInterrupt(CheckForInterruptsMask | INTERRUPT_WAIT_WAKEUP,
+								 WL_INTERRUPT | WL_EXIT_ON_PM_DEATH, 0,
+								 we_message_queue);
 			CHECK_FOR_INTERRUPTS();
 		}
 	}
diff --git a/src/test/modules/test_shm_mq/test_shm_mq.h b/src/test/modules/test_shm_mq/test_shm_mq.h
index b6a0290289c..6f0826be340 100644
--- a/src/test/modules/test_shm_mq/test_shm_mq.h
+++ b/src/test/modules/test_shm_mq/test_shm_mq.h
@@ -28,6 +28,8 @@
  */
 typedef struct
 {
+	ProcNumber	leader_proc_number;
+
 	slock_t		mutex;
 	int			workers_total;
 	int			workers_attached;
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index e13c05ae5c7..98ad61fc800 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,9 +19,8 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
-#include "storage/latch.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
 #include "storage/shm_mq.h"
@@ -54,7 +53,6 @@ test_shm_mq_main(Datum main_arg)
 	shm_mq_handle *outqh;
 	volatile test_shm_mq_header *hdr;
 	int			myworkernumber;
-	PGPROC	   *registrant;
 
 	/* Unblock signals.  The standard signal handlers are OK for us. */
 	BackgroundWorkerUnblockSignals();
@@ -118,13 +116,7 @@ test_shm_mq_main(Datum main_arg)
 	SpinLockAcquire(&hdr->mutex);
 	++hdr->workers_ready;
 	SpinLockRelease(&hdr->mutex);
-	registrant = BackendPidGetProc(MyBgworkerEntry->bgw_notify_pid);
-	if (registrant == NULL)
-	{
-		elog(DEBUG1, "registrant backend has exited prematurely");
-		proc_exit(1);
-	}
-	SetLatch(&registrant->procLatch);
+	SendInterrupt(INTERRUPT_WAIT_WAKEUP, hdr->leader_proc_number);
 
 	/* Do the work. */
 	copy_messages(inqh, outqh);
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index d1e4a2bd952..8f9fa9e1834 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -4,8 +4,8 @@
  *		Sample background worker code that demonstrates various coding
  *		patterns: establishing a database connection; starting and committing
  *		transactions; using GUC variables, and heeding SIGHUP to reread
- *		the configuration file; reporting to pg_stat_activity; using the
- *		process latch to sleep and exit in case of postmaster death.
+ *		the configuration file; reporting to pg_stat_activity; using
+ *		WaitInterrupt to sleep and exit in case of postmaster death.
  *
  * This code connects to a database, creates a schema and table, and summarizes
  * the numbers contained therein.  To see it working, insert an initial value
@@ -23,10 +23,8 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
-#include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
 #include "access/xact.h"
@@ -155,11 +153,10 @@ worker_spi_main(Datum main_arg)
 	p += sizeof(Oid);
 	memcpy(&flags, p, sizeof(bits32));
 
-	/* Establish signal handlers before unblocking signals. */
-	pqsignal(SIGHUP, SignalHandlerForConfigReload);
-	pqsignal(SIGTERM, die);
-
-	/* We're now ready to receive signals */
+	/*
+	 * The standard interrupt and signal handlers are OK for us; unblock
+	 * signals.
+	 */
 	BackgroundWorkerUnblockSignals();
 
 	/* Connect to our database */
@@ -213,26 +210,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
@@ -366,7 +362,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.
@@ -420,8 +415,6 @@ worker_spi_launch(PG_FUNCTION_ARGS)
 	snprintf(worker.bgw_name, BGW_MAXLEN, "worker_spi dynamic worker %d", i);
 	snprintf(worker.bgw_type, BGW_MAXLEN, "worker_spi dynamic");
 	worker.bgw_main_arg = Int32GetDatum(i);
-	/* set bgw_notify_pid so that we can use WaitForBackgroundWorkerStartup */
-	worker.bgw_notify_pid = MyProcPid;
 
 	/* extract flags, if any */
 	ndim = ARR_NDIM(arr);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 77e3c04144e..8a3543ccb87 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -1328,6 +1328,7 @@ Integer
 IntegerSet
 InternalDefaultACL
 InternalGrant
+InterruptMask
 Interval
 IntervalAggState
 IntoClause
@@ -1565,7 +1566,6 @@ LZ4State
 LabelProvider
 LagTracker
 LargeObjectDesc
-Latch
 LauncherLastStartTimesEntry
 LerpFunc
 LexDescr
@@ -2029,6 +2029,7 @@ PLyUnicode_FromStringAndSize_t
 PLy_elog_impl_t
 PMChild
 PMChildPool
+PMChildSlotData
 PMINIDUMP_CALLBACK_INFORMATION
 PMINIDUMP_EXCEPTION_INFORMATION
 PMINIDUMP_USER_STREAM_INFORMATION
-- 
2.47.3



  [text/x-patch] v12-0004-Introduce-PendingInterrupts-with-separate-flags-.patch (53.6K, ../../[email protected]/5-v12-0004-Introduce-PendingInterrupts-with-separate-flags-.patch)
  download | inline diff:
From 4264b94bd7da55fcee544596b751f2dca1796028 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 20 Feb 2026 16:14:09 +0200
Subject: [PATCH v12 4/4] Introduce PendingInterrupts with separate 'flags'
 field and "attention"

This does a couple of things:

- Split the interrupts mask into two 32-bit fields on
  PG_HAVE_ATOMIC_U64_SIMULATION systems. This fixes the self-deadlock
  risk when used from signal handlers.

- Introduce an "attention mask" field, where the backend can advertise
  the interrupts it is currently interested in, and a separate "flags"
  field. We no longer need to steal bits from the interrupt mask for
  the flags.

  Whenever a backend sends an interrupt, it checks the attention mask
  and only wakes up the backend if the interrupt is in the attention
  mask. When not sleeping, the same mechanism is used to set a flag
  for CHECK_FOR_INTERRUPTS(), telling the next CHECK_FOR_INTERRUPTS()
  that it has some work to do. By moving that work of checking the
  attention mask to the sender, CHECK_FOR_INTERRUPTS() needs fewer
  instructions.
---
 src/backend/access/spgist/spgdoinsert.c |   9 +-
 src/backend/access/transam/parallel.c   |   2 +-
 src/backend/ipc/interrupt.c             | 317 +++++++++++++++-----
 src/backend/storage/ipc/ipc.c           |   9 +-
 src/backend/storage/ipc/waiteventset.c  |  58 ++--
 src/backend/tcop/postgres.c             |   5 +-
 src/backend/utils/error/elog.c          |   4 +-
 src/include/ipc/interrupt.h             | 383 ++++++++++--------------
 src/include/ipc/standard_interrupts.h   | 194 ++++++++++++
 src/include/storage/proc.h              |   2 +-
 src/tools/pgindent/typedefs.list        |   1 +
 11 files changed, 634 insertions(+), 350 deletions(-)
 create mode 100644 src/include/ipc/standard_interrupts.h

diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index b8b5eaac57a..7c7371c69e8 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -2035,11 +2035,8 @@ spgdoinsert(Relation index, SpGistState *state,
 		 * pending, break out of the loop and deal with the situation below.
 		 * Set result = false because we must restart the insertion if the
 		 * interrupt isn't a query-cancel-or-die case.
-		 *
-		 * FIXME: CheckForInterruptsMask covers more than just query cancel
-		 * and die.  Could we be more precise here?
 		 */
-		if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
+		if (INTERRUPTS_PENDING_CONDITION())
 		{
 			result = false;
 			break;
@@ -2165,7 +2162,7 @@ spgdoinsert(Relation index, SpGistState *state,
 			 * repeatedly, check for query cancel (see comments above).
 			 */
 	process_inner_tuple:
-			if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask))
+			if (INTERRUPTS_PENDING_CONDITION())
 			{
 				result = false;
 				break;
@@ -2341,7 +2338,7 @@ spgdoinsert(Relation index, SpGistState *state,
 	 * were the case, telling the caller to retry would create an infinite
 	 * loop.
 	 */
-	Assert(INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask));
+	Assert(INTERRUPTS_CAN_BE_PROCESSED());
 
 	/*
 	 * Finally, check for interrupts again.  If there was a query cancel,
diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index c668a26d5a0..295a90e7d2a 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -238,7 +238,7 @@ InitializeParallelDSM(ParallelContext *pcxt)
 	 * We can deal with that edge case by pretending no workers were
 	 * requested.
 	 */
-	if (!INTERRUPTS_CAN_BE_PROCESSED(CheckForInterruptsMask))
+	if (!INTERRUPTS_CAN_BE_PROCESSED())
 		pcxt->nworkers = 0;
 
 	/*
diff --git a/src/backend/ipc/interrupt.c b/src/backend/ipc/interrupt.c
index 61d148409bc..cf901eb1d9d 100644
--- a/src/backend/ipc/interrupt.c
+++ b/src/backend/ipc/interrupt.c
@@ -22,29 +22,18 @@
 #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];
 
-/*
- * XXX: is 'volatile' still needed on all the variables below? Which ones are
- * accessed from signal handlers?
- */
-
 /* Bitmask of currently enabled interrupts */
-volatile InterruptMask EnabledInterruptsMask;
-
-/*
- * Interrupts that would be processed by CHECK_FOR_INTERRUPTS().  This is
- * equal to EnabledInterruptsMask, except when interrupts are held off by
- * HOLD/RESUME_INTERRUPTS() or a critical section.
- */
-volatile InterruptMask CheckForInterruptsMask;
-
-/* Variables for holdoff mechanism */
-volatile uint32 InterruptHoldoffCount = 0;
-volatile uint32 CritSectionCount = 0;
+InterruptMask EnabledInterruptsMask;
 
 /* A common WaitEventSet used to implement WaitInterrupt() */
 static WaitEventSet *InterruptWaitSet;
@@ -53,9 +42,10 @@ static WaitEventSet *InterruptWaitSet;
 #define InterruptWaitSetInterruptPos 0
 #define InterruptWaitSetPostmasterDeathPos 1
 
-static pg_atomic_uint64 LocalPendingInterrupts;
-
-pg_atomic_uint64 *MyPendingInterrupts = &LocalPendingInterrupts;
+static PendingInterrupts LocalPendingInterrupts;
+PendingInterrupts *MyPendingInterrupts = &LocalPendingInterrupts;
+pg_atomic_uint32 *MyPendingInterruptsFlags = &LocalPendingInterrupts.flags;
+const pg_atomic_uint32 ZeroPendingInterruptsFlags;
 
 static int	nextAddinInterruptBit = BEGIN_ADDIN_INTERRUPTS;
 
@@ -94,10 +84,8 @@ EnableInterrupt(InterruptMask interruptMask)
 			Assert(interrupt_handlers[i] != NULL);
 	}
 #endif
-
 	EnabledInterruptsMask |= interruptMask;
-	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
+	SetInterruptAttentionMask(EnabledInterruptsMask);
 }
 
 /*
@@ -112,67 +100,173 @@ 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)
-		CheckForInterruptsMask = EnabledInterruptsMask;
+		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.
+ * If an interrupt condition is pending, and it's safe to service it, then
+ * clear the flag and call the interrupt handler.
  *
- * Note: if INTERRUPTS_CAN_BE_PROCESSED(interrupt) is true, then
- * ProcessInterrupts is guaranteed to clear the given interrupt before
- * returning, if it was set when entering.  (This is not the same as
- * guaranteeing that it's still clear when we return; another interrupt could
- * have arrived.  But we promise that any pre-existing one will have been
- * serviced.)
+ * 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(InterruptHoldoffCount == 0 && CritSectionCount == 0);
+	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 */
-	interruptsToProcess =
-		pg_atomic_read_u64(MyPendingInterrupts) & CheckForInterruptsMask;
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_hi) << 32;
+#endif
+	interruptsToProcess = pending & EnabledInterruptsMask;
 
-	for (int i = 0; i < lengthof(interrupt_handlers); i++)
+	if (interruptsToProcess != 0)
 	{
-		if ((interruptsToProcess & UINT64_BIT(i)) != 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++)
 		{
-			/*
-			 * 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]) ();
+			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().
+	 */
 }
 
 /*
- * Switch to local interrupts.  Other backends can't send interrupts to this
- * one.  Only RaiseInterrupt() can set them, from inside this process.
+ * 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
-SwitchToLocalInterrupts(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)
 {
-	if (MyPendingInterrupts == &LocalPendingInterrupts)
+	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 = &LocalPendingInterrupts;
+	MyPendingInterrupts = new_ptr;
+	if (MyPendingInterruptsFlags == &old_ptr->flags)
+		MyPendingInterruptsFlags = &new_ptr->flags;
 
 	/*
 	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
@@ -181,14 +275,42 @@ SwitchToLocalInterrupts(void)
 	pg_memory_barrier();
 
 	/*
-	 * Mix in the interrupts that we have received already in our shared
-	 * interrupt vector, while atomically clearing it.  Other backends may
-	 * continue to set bits in it after this point, but we've atomically
+	 * 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.
 	 */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&MyProc->pendingInterrupts, 0));
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	{
+		uint64		old_interrupts;
+
+		old_interrupts = pg_atomic_exchange_u64(&old_ptr->interrupts, 0);
+		pg_atomic_fetch_or_u64(&new_ptr->interrupts, old_interrupts);
+	}
+#else
+	{
+		uint32		old_interrupts_lo;
+		uint32		old_interrupts_hi;
+
+		old_interrupts_lo = pg_atomic_exchange_u32(&old_ptr->interrupts_lo, 0);
+		old_interrupts_hi = pg_atomic_exchange_u32(&old_ptr->interrupts_hi, 0);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_lo, old_interrupts_lo);
+		pg_atomic_fetch_or_u32(&new_ptr->interrupts_hi, old_interrupts_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);
 }
 
 /*
@@ -198,20 +320,61 @@ SwitchToLocalInterrupts(void)
 void
 SwitchToSharedInterrupts(void)
 {
-	if (MyPendingInterrupts == &MyProc->pendingInterrupts)
-		return;
+	SwitchMyPendingInterruptsPtr(&MyProc->pendingInterrupts);
+}
 
-	MyPendingInterrupts = &MyProc->pendingInterrupts;
+static bool
+SendOrRaiseInterrupt(PendingInterrupts *ptr, InterruptMask interruptMask)
+{
+	uint64		old_pending;
+	uint64		attention_mask;
+	uint32		old_flags;
+	bool		wakeup = false;
 
 	/*
-	 * Make sure that SIGALRM handlers that call RaiseInterrupt() are now
-	 * seeing the new MyPendingInterrupts destination.
+	 * Do an "unlocked" read first, for a quick exit if all the bits are
+	 * already set.
 	 */
-	pg_memory_barrier();
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	old_pending = pg_atomic_read_u64(&ptr->interrupts);
+#else
+	old_pending = (uint64) pg_atomic_read_u32(&ptr->interrupts_lo);
+	old_pending |= (uint64) pg_atomic_read_u32(&ptr->interrupts_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->interrupts, interruptMask);
+#else
+	(void) pg_atomic_fetch_or_u32(&ptr->interrupts_lo, (uint32) interruptMask);
+	(void) pg_atomic_fetch_or_u32(&ptr->interrupts_hi, (uint32) (interruptMask >> 32));
+#endif
 
-	/* Mix in any unhandled bits from LocalPendingInterrupts. */
-	pg_atomic_fetch_or_u64(MyPendingInterrupts,
-						   pg_atomic_exchange_u64(&LocalPendingInterrupts, 0));
+	/*
+	 * 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;
 }
 
 /*
@@ -223,15 +386,7 @@ SwitchToSharedInterrupts(void)
 void
 RaiseInterrupt(InterruptMask interruptMask)
 {
-	uint64		old_pending;
-
-	old_pending = pg_atomic_fetch_or_u64(MyPendingInterrupts, interruptMask);
-
-	/*
-	 * If the process is currently blocked waiting for an interrupt to arrive,
-	 * and the interrupt wasn't already pending, wake it up.
-	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(MyPendingInterrupts, interruptMask))
 		WakeupMyProc();
 }
 
@@ -248,14 +403,12 @@ void
 SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 {
 	PGPROC	   *proc;
-	uint64		old_pending;
 
 	Assert(pgprocno != INVALID_PROC_NUMBER);
 	Assert(pgprocno >= 0);
 	Assert(pgprocno < ProcGlobal->allProcCount);
 
 	proc = &ProcGlobal->allProcs[pgprocno];
-	old_pending = pg_atomic_fetch_or_u64(&proc->pendingInterrupts, interruptMask);
 
 	elog(DEBUG1, "sending interrupt 0x016%" PRIx64 " to pid %d", interruptMask, proc->pid);
 
@@ -263,7 +416,7 @@ SendInterrupt(InterruptMask interruptMask, ProcNumber pgprocno)
 	 * If the process is currently blocked waiting for an interrupt to arrive,
 	 * and the interrupt wasn't already pending, wake it up.
 	 */
-	if ((old_pending & (interruptMask | SLEEPING_ON_INTERRUPTS)) == SLEEPING_ON_INTERRUPTS)
+	if (SendOrRaiseInterrupt(&proc->pendingInterrupts, interruptMask))
 		WakeupOtherProc(proc);
 }
 
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index 806947571ae..0e86491e1b6 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -173,14 +173,13 @@ proc_exit_prepare(int code)
 	proc_exit_inprogress = true;
 
 	/*
-	 * Forget any pending cancel or die requests and set InterruptHoldoffCount
-	 * to prevent any more interrupts from being processed; we're doing our
-	 * best to close up shop already.
+	 * 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.
 	 */
 	ClearInterrupt(INTERRUPT_TERMINATE);
 	ClearInterrupt(INTERRUPT_QUERY_CANCEL);
-	InterruptHoldoffCount = 1;
-	CritSectionCount = 0;
+	ResetInterruptHoldoffCounts(1, 0);
 
 	/*
 	 * Also clear the error context stack, to prevent error callbacks from
diff --git a/src/backend/storage/ipc/waiteventset.c b/src/backend/storage/ipc/waiteventset.c
index 612de8b35e9..bd52110389d 100644
--- a/src/backend/storage/ipc/waiteventset.c
+++ b/src/backend/storage/ipc/waiteventset.c
@@ -714,8 +714,7 @@ ModifyWaitEvent(WaitEventSet *set, int pos, uint32 events, InterruptMask interru
 		 * changes.  It means that when interrupt mask is set to 0, we will
 		 * listen on the kernel object unnecessarily, and might get some
 		 * spurious wakeups. The interrupt sending code should not wake us up
-		 * if the SLEEPING_ON_INTERRUPTS bit is not armed, though, so it
-		 * should be rare.
+		 * if the SLEEPING bit is not armed, though, so it should be rare.
 		 */
 		return;
 	}
@@ -1072,6 +1071,14 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	pgwin32_dispatch_queued_signals();
 #endif
 
+	/*
+	 * 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
@@ -1084,28 +1091,35 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 	 */
 	if (set->interrupt_mask != 0)
 	{
-		InterruptMask old_mask;
 		bool		already_pending = false;
 
 		/*
 		 * Perform a plain atomic read first as a fast path for the case that
 		 * an interrupt is already pending.
 		 */
-		old_mask = pg_atomic_read_u64(MyPendingInterrupts);
-		already_pending = ((old_mask & set->interrupt_mask) != 0);
+		already_pending = InterruptPending(set->interrupt_mask);
 
 		if (!already_pending)
 		{
 			/*
-			 * Atomically set the SLEEPING_ON_INTERRUPTS bit and re-check if
-			 * an interrupt is already pending. The atomic op provides
-			 * synchronization so that if an interrupt bit is set after this,
-			 * the setter will wake us up.
+			 * 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.
 			 */
-			old_mask = pg_atomic_fetch_or_u64(MyPendingInterrupts, SLEEPING_ON_INTERRUPTS);
-			already_pending = ((old_mask & set->interrupt_mask) != 0);
+#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();
+			already_pending = InterruptPending(set->interrupt_mask);
 
-			/* remember to clear the SLEEPING_ON_INTERRUPTS flag later */
+			/* Remember to clear the SLEEPING flag afterwards. */
 			sleeping_flag_armed = true;
 		}
 
@@ -1175,9 +1189,17 @@ WaitEventSetWait(WaitEventSet *set, long timeout,
 		}
 	}
 
-	/* If we set the SLEEPING_ON_INTERRUPTS flag, reset it again */
+	/*
+	 * If we slept, clear the SLEEPING flag again and reset the attention mask
+	 * for CHECK_FOR_INTERRUPTS()
+	 */
 	if (sleeping_flag_armed)
-		pg_atomic_fetch_and_u64(MyPendingInterrupts, ~((uint32) SLEEPING_ON_INTERRUPTS));
+	{
+		pg_atomic_write_u32(&MyPendingInterrupts->flags, 0);
+		SetInterruptAttentionMask(EnabledInterruptsMask);
+	}
+
+	RESUME_INTERRUPTS();
 
 #ifndef WIN32
 	waiting = false;
@@ -1255,7 +1277,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* Drain the signalfd. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u64(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1414,7 +1436,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 		if (cur_event->events == WL_INTERRUPT &&
 			cur_kqueue_event->filter == EVFILT_SIGNAL)
 		{
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1539,7 +1561,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			/* There's data in the self-pipe, clear it. */
 			drain();
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
@@ -1760,7 +1782,7 @@ WaitEventSetWaitBlock(WaitEventSet *set, int cur_timeout,
 			if (!ResetEvent(set->handles[cur_event->pos + 1]))
 				elog(ERROR, "ResetEvent failed: error code %lu", GetLastError());
 
-			if (set->interrupt_mask != 0 && (pg_atomic_read_u32(MyPendingInterrupts) & set->interrupt_mask) != 0)
+			if (set->interrupt_mask != 0 && InterruptPending(set->interrupt_mask))
 			{
 				occurred_events->fd = PGINVALID_SOCKET;
 				occurred_events->events = WL_INTERRUPT;
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5e595e51987..94e59aff4ea 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3202,11 +3202,11 @@ report_recovery_conflict(RecoveryConflictReason reason)
 		if (!DoingCommandRead)
 		{
 			/* Avoid losing sync in the FE/BE protocol. */
-			if (!INTERRUPTS_CAN_BE_PROCESSED(INTERRUPT_QUERY_CANCEL))
+			if ((EnabledInterruptsMask & INTERRUPT_QUERY_CANCEL) == 0)
 			{
 				/*
 				 * Re-arm and defer this interrupt until later.  See similar
-				 * code in ProcessInterrupts().
+				 * code in ProcessInterrupts(). FIXME: what similar code
 				 */
 				(void) pg_atomic_fetch_or_u32(&MyProc->pendingRecoveryConflicts, (1 << reason));
 				RaiseInterrupt(INTERRUPT_RECOVERY_CONFLICT);
@@ -3261,7 +3261,6 @@ void
 ProcessTerminateInterrupt(void)
 {
 	/* OK to accept any interrupts now? */
-	Assert(InterruptHoldoffCount == 0);
 	Assert(CritSectionCount == 0);
 
 	{
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index a36c7c33249..404e4247831 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -536,9 +536,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;
-
-		CritSectionCount = 0;	/* should be unnecessary, but... */
+		ResetInterruptHoldoffCounts(0, 0);
 
 		/*
 		 * Note that we leave CurrentMemoryContext set to ErrorContext. The
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
index 239d58f7597..10234d4f987 100644
--- a/src/include/ipc/interrupt.h
+++ b/src/include/ipc/interrupt.h
@@ -15,6 +15,7 @@
 #ifndef IPC_INTERRUPT_H
 #define IPC_INTERRUPT_H
 
+#include "ipc/standard_interrupts.h"
 #include "port/atomics.h"
 #include "storage/procnumber.h"
 
@@ -23,212 +24,128 @@
  * needed which are needed by all callers of WaitInterrupt, so include it
  * here.
  *
- * Note: InterruptMask is defind in waiteventset.h to avoid circular dependency
+ * Note: InterruptMask is defined in waiteventset.h to avoid circular dependency
  */
 #include "storage/waiteventset.h"
 
-
 /*
- * Flags in the pending interrupts bitmask. Each value is a different bit, so that
- * these can be conveniently OR'd together.
- */
-#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
-
-/***********************************************************************
- * Begin definitions of built-in interrupt bits
- ***********************************************************************/
-
-/*
- * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
- * a process, which don't need a dedicated interrupt bit.
- */
-#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
-
-
-/***********************************************************************
- * Standard interrupts handled the same by most processes
+ * PendingInterrupts is used to receive and wait for interrupts.  The
+ * 'interrupts' field is a bitmask representing interrupts that are currently
+ * pending for the process.
  *
- * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
- * process startup has reached SetStandardInterrupts().
- ***********************************************************************/
-
-/*
- * Backend has been requested to terminate gracefully.
+ * 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.)
  *
- * 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.
+ * Attention mechanism
+ * -------------------
  *
- * 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.
+ * 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().
  *
- * 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.
+ * 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 is set, it also
+ * wakes it up.
  *
- * 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.
+ * 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.
  *
- * The interrupt is processed whenever starting to read from the client, or
- * when interrupted while doing so.
+ * 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.  The system tolerates spuriously set ATTENTION flag and wakeups,
+ * so that's OK.  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.
  */
-#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.
- ***********************************************************************/
+typedef struct
+{
+	pg_atomic_uint32 flags;		/* PI_FLAG_* */
 
-/* ask walsenders to prepare for shutdown  */
-#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pg_atomic_uint64 interrupts;	/* pending interrupts */
+	pg_atomic_uint64 attention_mask;	/* interrupts that set the ATTENTION
+										 * flag */
+#else
+	pg_atomic_uint32 interrupts_lo;
+	pg_atomic_uint32 interrupts_hi;
+	pg_atomic_uint32 attention_mask_lo;
+	pg_atomic_uint32 attention_mask_hi;
+#endif
+} PendingInterrupts;
 
-/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
-#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+#define PI_FLAG_ATTENTION		0x01
+#define PI_FLAG_SLEEPING		0x02
 
 /*
- * 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.
+ * 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.
  */
-#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
-
-/* Wake up startup process to check for the promotion signal file */
-#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
-
-/* sent to logical replication launcher, when a subscription changes */
-#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
-
-/* Graceful shutdown request for a parallel apply worker */
-#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
-
-/* Request checkpointer to perform one last checkpoint, then shut down. */
-#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
-
-#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+extern PGDLLIMPORT PendingInterrupts *MyPendingInterrupts;
 
 /*
- * 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
+ * Test if an interrupt is pending
  *
- * The remaining bits are handed out by RequestAddinInterrupt, for
- * extensions
- ***********************************************************************/
-#define BEGIN_ADDIN_INTERRUPTS 25
-#define END_ADDIN_INTERRUPTS 63
-
-/*
- * SLEEPING_ON_INTERRUPTS indicates that the backend is currently blocked
- * waiting for an interrupt.  If set, the backend needs to be woken up when a
- * bit in the pending interrupts mask is set.  It's used internally by the
- * interrupt machinery, and cannot be used directly in the public functions.
- * It's named differently to distinguish it from the actual interrupt flags.
- */
-#define SLEEPING_ON_INTERRUPTS UINT64_BIT(63)
-
-extern PGDLLIMPORT pg_atomic_uint64 *MyPendingInterrupts;
-
-/*
- * Test an interrupt flag (or flags).
+ * 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. This is used in
-	 * CHECK_FOR_INTERRUPTS(), so we want this to be as cheap as possible.
-	 *
-	 * That means that if the interrupt is concurrently set by another
-	 * process, we might miss it. That should be OK, because the next
-	 * WaitInterrupt() or equivalent call acts as a synchronization barrier.
-	 * We will see the updated value before sleeping.
+	 * 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.
 	 */
-	return (pg_atomic_read_u64(MyPendingInterrupts) & interruptMask) != 0;
+	uint64		pending;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	pending = pg_atomic_read_u64(&MyPendingInterrupts->interrupts);
+#else
+	pending = (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_lo);
+	pending |= (uint64) pg_atomic_read_u32(&MyPendingInterrupts->interrupts_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)
 {
-	pg_atomic_fetch_and_u64(MyPendingInterrupts, ~interruptMask);
+	uint64		mask = ~interruptMask;
+
+#ifndef PG_HAVE_ATOMIC_U64_SIMULATION
+	(void) pg_atomic_fetch_and_u64(&MyPendingInterrupts->interrupts, mask);
+#else
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_lo, (uint32) mask);
+	(void) pg_atomic_fetch_and_u32(&MyPendingInterrupts->interrupts_hi, (uint32) (mask >> 32));
+#endif
 }
 
 /*
@@ -254,106 +171,110 @@ extern void SwitchToLocalInterrupts(void);
 extern void SwitchToSharedInterrupts(void);
 extern void InitializeInterruptWaitSet(void);
 
-typedef void (*pg_interrupt_handler_t) (void);
-extern void SetInterruptHandler(InterruptMask interruptMask, pg_interrupt_handler_t handler);
-
-extern void EnableInterrupt(InterruptMask interruptMask);
-extern void DisableInterrupt(InterruptMask interruptMask);
-
-/* for extensions */
-extern InterruptMask RequestAddinInterrupt(void);
-
-/* Standard interrupt handlers. Defined in tcop/postgres.c */
-extern void SetStandardInterruptHandlers(void);
-
-extern void ProcessQueryCancelInterrupt(void);
-extern void ProcessTerminateInterrupt(void);
-extern void ProcessConfigReloadInterrupt(void);
-extern void ProcessAsyncNotifyInterrupt(void);
-extern void ProcessIdleStatsTimeoutInterrupt(void);
-extern void ProcessRecoveryConflictInterrupts(void);
-extern void ProcessTransactionTimeoutInterrupt(void);
-extern void ProcessIdleSessionTimeoutInterrupt(void);
-extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
-extern void ProcessClientCheckTimeoutInterrupt(void);
-extern void ProcessClientConnectionLost(void);
-
-extern void ProcessAuxProcessShutdownInterrupt(void);
-
 
 /*****************************************************************************
  *	  CHECK_FOR_INTERRUPTS() and friends
  *****************************************************************************/
 
+/* Interrupts currently enabled for CHECK_FOR_INTERRUPTS() processing */
+extern PGDLLIMPORT InterruptMask EnabledInterruptsMask;
 
-extern PGDLLIMPORT volatile InterruptMask EnabledInterruptsMask;
-extern PGDLLIMPORT volatile InterruptMask CheckForInterruptsMask;
-
-extern void ProcessInterrupts(void);
+/*
+ * 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;
 
-/* Test whether an interrupt is pending */
+/*
+ * 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
+ * if the HOLD_INTERRUPTS() is delaying the interrupt processing.
+ */
 #ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION(mask) \
-	(unlikely(InterruptPending(mask)))
+#define INTERRUPTS_PENDING_CONDITION() \
+	(unlikely(InterruptPending(EnabledInterruptsMask)))
 #else
-#define INTERRUPTS_PENDING_CONDITION(mask) \
+#define INTERRUPTS_PENDING_CONDITION() \
 	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
 	 pgwin32_dispatch_queued_signals() : (void) 0,	\
-	 unlikely(InterruptPending(mask)))
+	 unlikely(InterruptPending(EnabledInterruptsMask)))
 #endif
 
 /*
- * Is ProcessInterrupts() guaranteed to clear all the bits in 'mask'?
- *
- * (The interrupt handler may re-raise the interrupt, though)
+ * Can interrupts be processed in the current state, i.e. are the interrupts
+ * not prevented by the HOLD_INTERRUPTS() or a critical section critical
+ * section?
  */
-#define INTERRUPTS_CAN_BE_PROCESSED(mask) \
-	(((mask) & CheckForInterruptsMask) == (mask))
+#define INTERRUPTS_CAN_BE_PROCESSED() \
+	(InterruptHoldoffCount == 0 && CritSectionCount == 0)
 
-/* Service interrupt, if one is pending and it's safe to service it now */
+/*
+ * 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.
+ *
+ * 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()					\
 do { \
-	if (INTERRUPTS_PENDING_CONDITION(CheckForInterruptsMask)) \
+	if (unlikely(pg_atomic_read_u32(MyPendingInterruptsFlags) != 0)) \
 		ProcessInterrupts();											\
 } while(0)
 
+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: */
-/*
- * XXX: is that still true? Should we use local vars to avoid repeated access
- * e.g. inside RESUME_INTERRUPTS() ?
- */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
+extern PGDLLIMPORT uint32 InterruptHoldoffCount;
+extern PGDLLIMPORT uint32 CritSectionCount;
+
+extern const pg_atomic_uint32 ZeroPendingInterruptsFlags;
 
 static inline void
 HOLD_INTERRUPTS(void)
 {
 	InterruptHoldoffCount++;
-	CheckForInterruptsMask = (InterruptMask) 0;
+
+	/*
+	 * Disable CHECK_FOR_INTERRUPTS() by pointing MyPendingInterruptsFlags to
+	 * an all-zeros constant.
+	 */
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
 RESUME_INTERRUPTS(void)
 {
-	Assert(CheckForInterruptsMask == 0);
 	Assert(InterruptHoldoffCount > 0);
 	InterruptHoldoffCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
-	else
-		Assert(CheckForInterruptsMask == 0);
+		MyPendingInterruptsFlags = &MyPendingInterrupts->flags;
 }
 
 static inline void
 START_CRIT_SECTION(void)
 {
 	CritSectionCount++;
-	CheckForInterruptsMask = 0;
+	MyPendingInterruptsFlags = unconstify(pg_atomic_uint32 *, &ZeroPendingInterruptsFlags);
 }
 
 static inline void
@@ -362,9 +283,9 @@ END_CRIT_SECTION(void)
 	Assert(CritSectionCount > 0);
 	CritSectionCount--;
 	if (InterruptHoldoffCount == 0 && CritSectionCount == 0)
-		CheckForInterruptsMask = EnabledInterruptsMask;
-	else
-		Assert(CheckForInterruptsMask == 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/standard_interrupts.h b/src/include/ipc/standard_interrupts.h
new file mode 100644
index 00000000000..8907b378f4f
--- /dev/null
+++ b/src/include/ipc/standard_interrupts.h
@@ -0,0 +1,194 @@
+#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->interrupts bitmask.  Each
+ * value is a different bit, so that these can be conveniently OR'd together.
+ */
+#define UINT64_BIT(shift) (UINT64_C(1) << (shift))
+
+/***********************************************************************
+ * Begin definitions of built-in interrupt bits
+ ***********************************************************************/
+
+/*
+ * INTERRUPT_WAIT_WAKEUP is shared by many use cases that need to wake up
+ * a process, which don't need a dedicated interrupt bit.
+ */
+#define INTERRUPT_WAIT_WAKEUP					UINT64_BIT(0)
+
+
+/***********************************************************************
+ * Standard interrupts handled the same by most processes
+ *
+ * Most of these are normally processed by CHECK_FOR_INTERRUPTS() once
+ * process startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/*
+ * Backend has been requested to terminate gracefully.
+ *
+ * This is raised by the SIGTERM signal handler, or can be sent directly by
+ * another backend e.g. with pg_terminate_backend().
+ */
+#define INTERRUPT_TERMINATE						UINT64_BIT(1)
+
+/*
+ * Cancel current query, if any.
+ *
+ * Sent to regular backends by pg_cancel_backend(), SIGINT, or in response to
+ * a query cancellation packet.  Some other processes like autovacuum workers
+ * and logical decoding processes also react to this.
+ */
+#define INTERRUPT_QUERY_CANCEL					UINT64_BIT(2)
+
+/*
+ * Recovery conflict. This is sent by the startup process in hot standby mode
+ * when a backend holds back the WAL replay for too long. The reason for the
+ * conflict indicated by the PGPROC->pendingRecoveryConflicts
+ * bitmask. Conflicts are generally resolved by terminating the current query
+ * or session. The exact reaction depends on the reason and what state the
+ * backend is in.
+ */
+#define INTERRUPT_RECOVERY_CONFLICT 			UINT64_BIT(3)
+
+/*
+ * Config file reload is requested.
+ *
+ * This is normally disabled and therefore not handled at
+ * CHECK_FOR_INTERRUPTS(). The "main loop" in each process is expected to
+ * check for it explicitly.
+ */
+#define INTERRUPT_CONFIG_RELOAD					UINT64_BIT(4)
+
+/*
+ * Log current memory contexts, sent by pg_log_backend_memory_contexts()
+ */
+#define INTERRUPT_LOG_MEMORY_CONTEXT 			UINT64_BIT(5)
+
+/*
+ * procsignal global barrier interrupt
+ */
+#define INTERRUPT_BARRIER						UINT64_BIT(6)
+
+
+/***********************************************************************
+ * Interrupts used by client backends and most other processes that
+ * connect to a particular database.
+ *
+ * Most of these are also processed by CHECK_FOR_INTERRUPTS() once process
+ * startup has reached SetStandardInterrupts().
+ ***********************************************************************/
+
+/* Raised by timers */
+#define INTERRUPT_TRANSACTION_TIMEOUT			UINT64_BIT(7)
+#define INTERRUPT_IDLE_SESSION_TIMEOUT			UINT64_BIT(8)
+#define INTERRUPT_IDLE_IN_TRANSACTION_SESSION_TIMEOUT UINT64_BIT(9)
+#define INTERRUPT_CLIENT_CHECK_TIMEOUT			UINT64_BIT(10)
+
+/* Raised by timer while idle, to send a stats update */
+#define INTERRUPT_IDLE_STATS_TIMEOUT			UINT64_BIT(11)
+
+/* Raised synchronously when the client connection is lost */
+#define INTERRUPT_CLIENT_CONNECTION_LOST		 UINT64_BIT(12)
+
+/*
+ * INTERRUPT_ASYNC_NOTIFY is sent to notify backends that have registered to
+ * LISTEN on any channels that they might have messages they need to deliver
+ * to the frontend. It is also processed whenever starting to read from the
+ * client or while doing so, but only when there is no transaction in
+ * progress.
+ */
+#define INTERRUPT_ASYNC_NOTIFY					UINT64_BIT(13)
+
+/*
+ * Because backends sitting idle will not be reading sinval events, we need a
+ * way to give an idle backend a swift kick in the rear and make it catch up
+ * before the sinval queue overflows and forces it to go through a cache reset
+ * exercise.  This is done by sending INTERRUPT_SINVAL_CATCHUP to any backend
+ * that gets too far behind.
+ *
+ * The interrupt is processed whenever starting to read from the client, or
+ * when interrupted while doing so.
+ */
+#define INTERRUPT_SINVAL_CATCHUP				UINT64_BIT(14)
+
+/* Message from a cooperating parallel backend or apply worker */
+#define INTERRUPT_PARALLEL_MESSAGE				UINT64_BIT(15)
+
+
+/***********************************************************************
+ * Process-specific interrupts
+ *
+ * Some processes need dedicated interrupts for various purposes.  Ignored
+ * by other processes.
+ ***********************************************************************/
+
+/* ask walsenders to prepare for shutdown  */
+#define INTERRUPT_WALSND_INIT_STOPPING			UINT64_BIT(16)
+
+/* TODO: document the difference with INTERRUPT_WALSND_INIT_STOPPING */
+#define INTERRUPT_WALSND_STOP					UINT64_BIT(17)
+
+/*
+ * INTERRUPT_WAL_ARRIVED is used to wake up the startup process, to tell
+ * it that it should continue WAL replay. It's sent by WAL receiver when
+ * more WAL arrives, or when promotion is requested.
+ */
+#define INTERRUPT_WAL_ARRIVED					UINT64_BIT(18)
+
+/* Wake up startup process to check for the promotion signal file */
+#define INTERRUPT_CHECK_PROMOTE					UINT64_BIT(19)
+
+/* sent to logical replication launcher, when a subscription changes */
+#define INTERRUPT_SUBSCRIPTION_CHANGE			UINT64_BIT(20)
+
+/* Graceful shutdown request for a parallel apply worker */
+#define INTERRUPT_SHUTDOWN_PARALLEL_APPLY_WORKER UINT64_BIT(21)
+
+/* Request checkpointer to perform one last checkpoint, then shut down. */
+#define INTERRUPT_SHUTDOWN_XLOG					UINT64_BIT(22)
+
+#define INTERRUPT_SHUTDOWN_PGARCH				UINT64_BIT(23)
+
+/*
+ * This is sent to the autovacuum launcher when an autovacuum worker exits
+ */
+#define INTERRUPT_AUTOVACUUM_WORKER_FINISHED 	UINT64_BIT(24)
+
+
+/***********************************************************************
+ * End of built-in interrupt bits
+ *
+ * The remaining bits are handed out by RequestAddinInterrupt, for
+ * extensions
+ ***********************************************************************/
+#define BEGIN_ADDIN_INTERRUPTS 25
+#define END_ADDIN_INTERRUPTS 64
+
+
+/* for extensions */
+extern InterruptMask RequestAddinInterrupt(void);
+
+/* Standard interrupt handlers. Defined in tcop/postgres.c */
+extern void SetStandardInterruptHandlers(void);
+
+extern void ProcessQueryCancelInterrupt(void);
+extern void ProcessTerminateInterrupt(void);
+extern void ProcessConfigReloadInterrupt(void);
+extern void ProcessAsyncNotifyInterrupt(void);
+extern void ProcessIdleStatsTimeoutInterrupt(void);
+extern void ProcessRecoveryConflictInterrupts(void);
+extern void ProcessTransactionTimeoutInterrupt(void);
+extern void ProcessIdleSessionTimeoutInterrupt(void);
+extern void ProcessIdleInTransactionSessionTimeoutInterrupt(void);
+extern void ProcessClientCheckTimeoutInterrupt(void);
+extern void ProcessClientConnectionLost(void);
+
+extern void ProcessAuxProcessShutdownInterrupt(void);
+
+
+#endif							/* IPC_STANDARD_INTERRUPTS_H */
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 1ca4c70715c..2590006270e 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -255,7 +255,7 @@ typedef struct PGPROC
 	int			delayChkptFlags;	/* for DELAY_CHKPT_* flags */
 
 	/* Bit mask of pending interrupts, waiting to be processed */
-	pg_atomic_uint64 pendingInterrupts;
+	PendingInterrupts pendingInterrupts;
 
 #ifdef WIN32
 	/* Event handle to wake up the process when sending an interrupt */
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 8a3543ccb87..259ea155b03 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2185,6 +2185,7 @@ PatternInfoArray
 Pattern_Prefix_Status
 Pattern_Type
 PendingFsyncEntry
+PendingInterrupts
 PendingListenAction
 PendingListenEntry
 PendingRelDelete
-- 
2.47.3



^ permalink  raw  reply  [nested|flat] 22+ messages in thread

* Re: Interrupts vs signals
  2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
  2026-02-18 00:11               ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
  2026-02-20 14:22                 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
@ 2026-06-24 20:25                   ` Heikki Linnakangas <[email protected]>
  1 sibling, 0 replies; 22+ messages in thread

From: Heikki Linnakangas @ 2026-06-24 20:25 UTC (permalink / raw)
  To: Andres Freund <[email protected]>; +Cc: Thomas Munro <[email protected]>; Michael Paquier <[email protected]>; Robert Haas <[email protected]>; Fujii Masao <[email protected]>; pgsql-hackers

On 10/03/2026 21:43, Andres Freund wrote:
> On 2026-02-20 16:22:48 +0200, Heikki Linnakangas wrote:
>> Patch attached. The relevant changes for this "attention mechanism" are in
>> the last patch, but there are some other small changes too so this split
>> into patches is a little messy. I moved the list of standard interrupts to a
>> separate header file, for example. So for reviewing, I recommend reading the
>> resulting interrupt.h and interrupt.c files after applying all the patches,
>> instead of trying to read the diff for those.
> 
> I was just looking at these patches. They needed a rebase. Attached without
> other changes.

Thanks! Here's a new version, rebased again and with a big bunch of 
little fixes.

> A few comments:
> 
> - I don't think I really understand the interrupts.h / standard_interrupts.h
>    split.  Also, the latter doesn't have the normal function header etc.

Added standard file header. The idea is that "standard_interrupts.h" 
contains the definitions of all the different interrupt, and prototypes 
for functions related to them, whereas "interrupts.h" contains 
definitions for the interrupt system as whole, with no knowledge of what 
any of the individual bits mean.

> - It probably doesn't matter, but it seems that for SendOrRaiseInterrupt()
>    could we skip the setting of PI_FLAG_ATTENTION if already set?

Hmm, I guess, but I think it'd just make it more complicated to try to 
check for that.

> - I see a bunch of core files like this:
> 
> 
> #4  0x0000000000d6a4d8 in ExceptionalCondition (conditionName=0x1089ff8 "(pg_atomic_read_u32(&MyPendingInterrupts->flags) & PI_FLAG_SLEEPING) == 0",
>     fileName=0x1089f70 "../../../../../home/andres/src/postgresql/src/backend/ipc/interrupt.c", lineNumber=262)
>     at ../../../../../home/andres/src/postgresql/src/backend/utils/error/assert.c:65
> #5  0x000000000091aa12 in SwitchMyPendingInterruptsPtr (new_ptr=0x161dc30 <LocalPendingInterrupts>) at ../../../../../home/andres/src/postgresql/src/backend/ipc/interrupt.c:262
> #6  0x000000000091aaaa in SwitchToLocalInterrupts () at ../../../../../home/andres/src/postgresql/src/backend/ipc/interrupt.c:313
> #7  0x0000000000b71dca in AuxiliaryProcKill (code=1, arg=4) at ../../../../../home/andres/src/postgresql/src/backend/storage/lmgr/proc.c:1075
> #8  0x0000000000b411c4 in shmem_exit (code=1) at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/ipc.c:282
> #9  0x0000000000b40faa in proc_exit_prepare (code=1) at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/ipc.c:198
> #10 0x0000000000b40ef6 in proc_exit (code=1) at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/ipc.c:113
> #11 0x0000000000b53ba6 in WaitEventSetWaitBlock (set=0x257a1b40, cur_timeout=200, occurred_events=0x7fff8d4db130, nevents=1)
>     at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/waiteventset.c:1306
> #12 0x0000000000b53922 in WaitEventSetWait (set=0x257a1b40, timeout=200, occurred_events=0x7fff8d4db130, nevents=1, wait_event_info=83886083)
>     at ../../../../../home/andres/src/postgresql/src/backend/storage/ipc/waiteventset.c:1170
> #13 0x000000000091ae79 in WaitInterrupt (interruptMask=122, wakeEvents=41, timeout=200, wait_event_info=83886083)
>     at ../../../../../home/andres/src/postgresql/src/backend/ipc/interrupt.c:482
> #14 0x0000000000a7629c in BackgroundWriterMain (startup_data=0x0, startup_data_len=0) at ../../../../../home/andres/src/postgresql/src/backend/postmaster/bgwriter.c:298
> #15 0x0000000000a7869a in postmaster_child_launch (child_type=B_BG_WRITER, child_slot=35, startup_data=0x0, startup_data_len=0, client_sock=0x0)
>     at ../../../../../home/andres/src/postgresql/src/backend/postmaster/launch_backend.c:268
> 
> 
>   interestingly that doesn't trigger any test failures.

A-ha, yeah, that doesn't cause test failure because that happens in the 
WL_EXIT_ON_PM_DEATH case. Fixed by clearing the PI_FLAG_SLEEPING flag 
before bailing out in WaitEventSetWait() subroutines.

> - Are we generally ok with having no memory barriers around
>    CHECK_FOR_INTERRUPTS()/InterruptPending()/ConsumeInterrupt()? It might be,
>    because we shouldn't rely on immediate processing of the interrupts, but at
>    the very least we need some documentation for why that is true.

I think so. There's a comment in InterruptPending() about that. We could 
make it more prominent by moving it to CHECK_FOR_INTERRUPTS(). I'm not 
sure if this can ever be a problem in practice though.

>    Practically it's probably ok, even on architectures with loose memory
>    ordering, we don't typically have loops that do something like
> 
>    while (true)
>    {
>        CFI();
>        very_cpu_intensive_but_nothing_else();
>    }
> 
>    And once you actually wait for an interrupt, lock a page, or ... we'll be
>    guaranteed to see the interrupt.

Hmm, even without a barrier, that won't get stuck forever, right? 
InterruptPending() uses pg_atomic_read_u64(), which still forces the 
value to be read on each iteration (the arg is 'volatile'). There's no 
cache coherency but I believe the CPU will *eventually* see the new 
value. I don't have a good idea of what "eventually" means on different 
architectures, but I'd guess << 1 s.

> - CFI now is:
> 
>    ; determine address of MyPendingInterruptsFlags
>    mov    0x0(%rip),%rax        # 3c9 <ExecScan+0x3c9>
>                          3c5: R_X86_64_REX_GOTPCRELX     MyPendingInterruptsFlags-0x4
> 
>    ; load *MyPendingInterruptsFlags
>    mov    (%rax),%rax
> 
>    ; load the contents of flags
>    mov    (%rax),%eax
> 
>    ; test if flags is 0
>    test   %eax,%eax
> 
>    ; jump if flags are set
>    jne    51b <ExecScan+0x51b>
> 
> 
>    That's one indirection worse than what you had in
>    https://www.postgresql.org/message-id/78c102b3-f1a6-4c70-a46f-2f04221b6193%40iki.fi
> 
>    I don't know if it matters.

AFAICS it's three indirections in both. Here are both again, with the 
indirections numbered:

OLD:

       ; load address of MyPendingInterrupts->flags
(1)   lea    0x433f0f(%rip),%rax        # 0x9728a0 <MyPendingInterrupts>
       ; load value of MyPendingInterrupts->flags
(2)   mov    (%rax),%rax
       ; test if MyPendingInterrupts->flags == 0
(3)   cmpl   $0x0,(%rax)
       ; jump for ProcessInterrupts
       jne    0x53e9fd <makeItemUnary+173>

NEW:

       ; determine address of MyPendingInterruptsFlags
(1)   mov    0x0(%rip),%rax        # 3c9 <ExecScan+0x3c9>
                           3c5: R_X86_64_REX_GOTPCRELX 
MyPendingInterruptsFlags-0x4
       ; load *MyPendingInterruptsFlags
(2)   mov    (%rax),%rax
       ; load the contents of flags
(3)   mov    (%rax),%eax
       ; test if flags is 0
       test   %eax,%eax
       ; jump if flags are set
       jne    51b <ExecScan+0x51b>

> - This continues to be a very very large commit.  As I IIRC suggested before,
>    I'd at least introduce ipc/interrupt.h (including adding the #includes for
>    it)

Done. There's now a separate commit to move CHECK_FOR_INTERRUPTS() to 
ipc/interrupt.h, and a few other preparatory refactorings.

>    I'd probably go further and introduce the new infra separately from
>    switching over to it, but...

I feel the intermediate state would be too confusing to really help with 
the review.

> - Wonder if it's worth putting the WIN32 stuff from InitProcGlobal() into its
>    own helper?  Probably not, at least until there's another platform specific
>    thing?

(InitProcGlobal() is called ProcGlobalShmemInit() now) It seems fine to 
me as it is. There's not much WIN32-specific code there, or elsewhere in 
these patches.

> - Does it matter that we have a bunch of code that does
>    if (InterruptPending(a))
>        stuff();
>    if (InterruptPending(b))
>        stuff();
>    if (ConsumeInterrupt(c))
>        stuff();
>    if (ConsumeInterrupt(d))
>        stuff();
> 
>    that does end up rereading the same variable from shared memory
>    repeatedly...

Hmm, it's probably fine, but if there's some place where that's 
performance critical, you could do a quick combined check first:

if (InterruptPending(a | b | c | d))
{
     if (InterruptPending(a))
         stuff();
     if (InterruptPending(b))
         stuff();
     if (ConsumeInterrupt(c))
         stuff();
     if (ConsumeInterrupt(d))
         stuff();
}

Or we could add a function to return the pending mask so that you'd only 
need to read it once. I haven't really felt the need for that so far, 
but it would seem pretty logical to have one.

> - Personally I rather dislike anonymous struct types (like PendingInterrupts),
>    as some debuggers and similar tooling won't be able to show names for them.

Fixed

> - I wonder if we should abstract the PG_HAVE_ATOMIC_U64_SIMULATION crap
>    somehow (or just remove platforms without it, but I don't think I want to be
>    the one doing that fight)

I think it's fine as it is, there are only a few functions that need to 
check PG_HAVE_ATOMIC_U64_SIMULATION currently. (I'd be all in favor of 
desupporting PG_HAVE_ATOMIC_U64_SIMULATION though)

- Heikki


Attachments:

  [text/x-patch] v13-0001-Refactor-how-some-aux-processes-advertise-their-.patch (9.6K, ../../[email protected]/2-v13-0001-Refactor-how-some-aux-processes-advertise-their-.patch)
  download | inline diff:
From 6efef631c6747b8c60cdeae543b4cd91f06da2e4 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Fri, 13 Feb 2026 14:23:35 +0200
Subject: [PATCH v13 1/9] Refactor how some aux processes advertise their
 ProcNumber

This moves the responsibility of setting the
ProcGlobal->walrewriterProc and checkpointerProc fields to
InitAuxiliaryProcess. Also switch to the same pattern to advertise the
autovacuum launcher's ProcNumber, replacing the ad hoc av_launcherpid
field in shared memory. This can easily be extended to other aux
processes in the future, if other processes need to find them.

Switch to pg_atomic_uint32 for the fields. Seems easier to reason
about than volatile pointers. There was some precedence for that, as
were already using pg_atomic_uint32 for the procArrayGroupFirst and
clogGroupFirst fields, which also store ProcNumbers.

TODO: could also replace WalRecv->procno with this
---
 src/backend/access/transam/xlog.c     |  3 +--
 src/backend/postmaster/autovacuum.c   | 19 +++++++++--------
 src/backend/postmaster/checkpointer.c | 14 ++++---------
 src/backend/postmaster/walwriter.c    |  6 ------
 src/backend/storage/lmgr/proc.c       | 30 +++++++++++++++++++++++++--
 src/include/storage/proc.h            |  7 ++++---
 6 files changed, 47 insertions(+), 32 deletions(-)

diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a81912b7441..a8bbf6284a7 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -2671,8 +2671,7 @@ XLogSetAsyncXactLSN(XLogRecPtr asyncXactLSN)
 
 	if (wakeup)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	walwriterProc = procglobal->walwriterProc;
+		ProcNumber	walwriterProc = pg_atomic_read_u32(&ProcGlobal->walwriterProc);
 
 		if (walwriterProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(walwriterProc)->procLatch);
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index e9aaf24c1be..777acaa7c84 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -286,7 +286,6 @@ typedef struct AutoVacuumWorkItem
  * struct and the array of WorkerInfo structs.  This struct keeps:
  *
  * av_signal		set by other processes to indicate various conditions
- * av_launcherpid	the PID of the autovacuum launcher
  * av_freeWorkers	the WorkerInfo freelist
  * av_runningWorkers the WorkerInfo non-free queue
  * av_startingWorker pointer to WorkerInfo currently being started (cleared by
@@ -302,7 +301,6 @@ typedef struct AutoVacuumWorkItem
 typedef struct
 {
 	sig_atomic_t av_signal[AutoVacNumSignals];
-	pid_t		av_launcherpid;
 	dclist_head av_freeWorkers;
 	dlist_head	av_runningWorkers;
 	WorkerInfo	av_startingWorker;
@@ -602,8 +600,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 		proc_exit(0);			/* done */
 	}
 
-	AutoVacuumShmem->av_launcherpid = MyProcPid;
-
 	/*
 	 * Create the initial database list.  The invariant we want this list to
 	 * keep is that it's ordered by decreasing next_worker.  As soon as an
@@ -837,8 +833,6 @@ AutoVacLauncherShutdown(void)
 {
 	ereport(DEBUG1,
 			(errmsg_internal("autovacuum launcher shutting down")));
-	AutoVacuumShmem->av_launcherpid = 0;
-
 	proc_exit(0);				/* done */
 }
 
@@ -1569,6 +1563,8 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	if (AutoVacuumShmem->av_startingWorker != NULL)
 	{
+		ProcNumber	launcherProc;
+
 		MyWorkerInfo = AutoVacuumShmem->av_startingWorker;
 		dbid = MyWorkerInfo->wi_dboid;
 		MyWorkerInfo->wi_proc = MyProc;
@@ -1587,8 +1583,14 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 		on_shmem_exit(FreeWorkerInfo, 0);
 
 		/* wake up the launcher */
-		if (AutoVacuumShmem->av_launcherpid != 0)
-			kill(AutoVacuumShmem->av_launcherpid, SIGUSR2);
+		launcherProc = pg_atomic_read_u32(&ProcGlobal->avLauncherProc);
+		if (launcherProc != INVALID_PROC_NUMBER)
+		{
+			int			pid = GetPGProcByNumber(launcherProc)->pid;
+
+			if (pid != 0)
+				kill(pid, SIGUSR2);
+		}
 	}
 	else
 	{
@@ -3559,7 +3561,6 @@ AutoVacuumShmemInit(void *arg)
 {
 	WorkerInfo	worker;
 
-	AutoVacuumShmem->av_launcherpid = 0;
 	dclist_init(&AutoVacuumShmem->av_freeWorkers);
 	dlist_init(&AutoVacuumShmem->av_runningWorkers);
 	AutoVacuumShmem->av_startingWorker = NULL;
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 087120db090..a09d4097d51 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -47,6 +47,7 @@
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "pgstat.h"
+#include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
 #include "postmaster/interrupt.h"
@@ -358,12 +359,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	 */
 	UpdateSharedMemoryConfig();
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->checkpointerProc = MyProcNumber;
-
 	/*
 	 * Loop until we've been asked to write the shutdown checkpoint or
 	 * terminate.
@@ -1120,7 +1115,7 @@ RequestCheckpoint(int flags)
 	for (ntries = 0;; ntries++)
 	{
 		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 		if (checkpointerProc == INVALID_PROC_NUMBER)
 		{
@@ -1261,8 +1256,7 @@ ForwardSyncRequest(const FileTag *ftag, SyncRequestType type)
 	/* ... but not till after we release the lock */
 	if (too_full)
 	{
-		volatile PROC_HDR *procglobal = ProcGlobal;
-		ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+		ProcNumber	checkpointerProc = pg_atomic_read_u32(&ProcGlobal->checkpointerProc);
 
 		if (checkpointerProc != INVALID_PROC_NUMBER)
 			SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
@@ -1543,7 +1537,7 @@ void
 WakeupCheckpointer(void)
 {
 	volatile PROC_HDR *procglobal = ProcGlobal;
-	ProcNumber	checkpointerProc = procglobal->checkpointerProc;
+	ProcNumber	checkpointerProc = pg_atomic_read_u32(&procglobal->checkpointerProc);
 
 	if (checkpointerProc != INVALID_PROC_NUMBER)
 		SetLatch(&GetPGProcByNumber(checkpointerProc)->procLatch);
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index af24d05c542..68dd5047c20 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -206,12 +206,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	hibernating = false;
 	SetWalWriterSleeping(false);
 
-	/*
-	 * Advertise our proc number that backends can use to wake us up while
-	 * we're sleeping.
-	 */
-	ProcGlobal->walwriterProc = MyProcNumber;
-
 	/*
 	 * Loop forever
 	 */
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 6fa9de33e1c..28c0efc3e20 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -240,8 +240,9 @@ ProcGlobalShmemInit(void *arg)
 	dlist_init(&ProcGlobal->bgworkerFreeProcs);
 	dlist_init(&ProcGlobal->walsenderFreeProcs);
 	ProcGlobal->startupBufferPinWaitBufId = -1;
-	ProcGlobal->walwriterProc = INVALID_PROC_NUMBER;
-	ProcGlobal->checkpointerProc = INVALID_PROC_NUMBER;
+	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->procArrayGroupFirst, INVALID_PROC_NUMBER);
 	pg_atomic_init_u32(&ProcGlobal->clogGroupFirst, INVALID_PROC_NUMBER);
 
@@ -725,6 +726,14 @@ InitAuxiliaryProcess(void)
 	 */
 	PGSemaphoreReset(MyProc->sem);
 
+	/* Some aux processes are also advertised in ProcGlobal */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, MyProcNumber);
+	if (MyBackendType == B_WAL_WRITER)
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, MyProcNumber);
+	if (MyBackendType == B_CHECKPOINTER)
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, MyProcNumber);
+
 	/*
 	 * Arrange to clean up at process exit.
 	 */
@@ -1102,6 +1111,23 @@ AuxiliaryProcKill(int code, Datum arg)
 	SwitchBackToLocalLatch();
 	pgstat_reset_wait_event_storage();
 
+	/* If this was one of aux processes advertised in ProcGlobal, clear it */
+	if (MyBackendType == B_AUTOVAC_LAUNCHER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->avLauncherProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->avLauncherProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_WAL_WRITER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->walwriterProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->walwriterProc, INVALID_PROC_NUMBER);
+	}
+	if (MyBackendType == B_CHECKPOINTER)
+	{
+		Assert(pg_atomic_read_u32(&ProcGlobal->checkpointerProc) == MyProcNumber);
+		pg_atomic_write_u32(&ProcGlobal->checkpointerProc, INVALID_PROC_NUMBER);
+	}
+
 	proc = MyProc;
 	MyProc = NULL;
 	MyProcNumber = INVALID_PROC_NUMBER;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 3e1d1fad5f9..0314f979e60 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -488,11 +488,12 @@ typedef struct PROC_HDR
 	pg_atomic_uint32 clogGroupFirst;
 
 	/*
-	 * Current slot numbers of some auxiliary processes. There can be only one
+	 * Current proc numbers of some auxiliary processes. There can be only one
 	 * of each of these running at a time.
 	 */
-	ProcNumber	walwriterProc;
-	ProcNumber	checkpointerProc;
+	pg_atomic_uint32 avLauncherProc;
+	pg_atomic_uint32 walwriterProc;
+	pg_atomic_uint32 checkpointerProc;
 
 	/* Current shared estimate of appropriate spins_per_delay value */
 	int			spins_per_delay;
-- 
2.47.3



  [text/x-patch] v13-0002-Centralize-resetting-SIGCHLD-handler.patch (9.8K, ../../[email protected]/3-v13-0002-Centralize-resetting-SIGCHLD-handler.patch)
  download | inline diff:
From 72bbc1428c4da0dbfdbbe9a506917e687acbc448 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 16 Jun 2026 22:06:39 +0300
Subject: [PATCH v13 2/9] Centralize resetting SIGCHLD handler

---
 src/backend/postmaster/autovacuum.c        |  2 --
 src/backend/postmaster/bgworker.c          |  1 -
 src/backend/postmaster/bgwriter.c          |  5 -----
 src/backend/postmaster/checkpointer.c      |  5 -----
 src/backend/postmaster/pgarch.c            |  3 ---
 src/backend/postmaster/startup.c           |  5 -----
 src/backend/postmaster/syslogger.c         |  5 -----
 src/backend/postmaster/walsummarizer.c     |  5 -----
 src/backend/postmaster/walwriter.c         |  5 -----
 src/backend/replication/logical/slotsync.c |  1 -
 src/backend/replication/walreceiver.c      |  3 ---
 src/backend/replication/walsender.c        |  3 ---
 src/backend/tcop/postgres.c                |  7 -------
 src/backend/utils/init/miscinit.c          | 14 ++++++++++++++
 14 files changed, 14 insertions(+), 50 deletions(-)

diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 777acaa7c84..31cad5df015 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -447,7 +447,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, avl_sigusr2_handler);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, PG_SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
@@ -1454,7 +1453,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, PG_SIG_IGN);
 	pqsignal(SIGFPE, FloatExceptionHandler);
-	pqsignal(SIGCHLD, PG_SIG_DFL);
 
 	/*
 	 * Create a per-backend PGPROC struct in shared memory.  We must do this
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 2e4acad4f00..2f51814c93d 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -797,7 +797,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
 
 	pqsignal(SIGPIPE, PG_SIG_IGN);
 	pqsignal(SIGUSR2, PG_SIG_IGN);
-	pqsignal(SIGCHLD, PG_SIG_DFL);
 
 	/*
 	 * If an exception is encountered, processing resumes here.
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index d364382303a..d6121f2b4fe 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -109,11 +109,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, PG_SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/*
 	 * We just started, assume there has been either a shutdown or
 	 * end-of-recovery snapshot.
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index a09d4097d51..4b051eb3128 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -231,11 +231,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, SignalHandlerForShutdownRequest);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/*
 	 * Initialize so that first time-driven event happens at the correct time.
 	 */
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 0f207ac0356..a3fd4f83201 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -237,9 +237,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, pgarch_waken_stop);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/* Unblock signals (they were blocked when the postmaster forked us) */
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index d91b7caac01..63624e8caa1 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -234,11 +234,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, StartupProcTriggerHandler);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/*
 	 * Register timeouts needed for standby mode
 	 */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 7645c495a81..b399adcce75 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -303,11 +303,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, sigUsr1Handler);	/* request log rotation */
 	pqsignal(SIGUSR2, PG_SIG_IGN);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	sigprocmask(SIG_SETMASK, &UnBlockSig, NULL);
 
 #ifdef WIN32
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 4f12eaf2c85..89301815dc3 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -264,11 +264,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
 									ALLOCSET_DEFAULT_SIZES);
 	MemoryContextSwitchTo(context);
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/*
 	 * If an exception is encountered, processing resumes here.
 	 */
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 68dd5047c20..3493be8e630 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -109,11 +109,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, PG_SIG_IGN);	/* not used */
 
-	/*
-	 * Reset some signals that are accepted by postmaster but not here
-	 */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/*
 	 * Create a memory context that we will do all our work in.  We do this so
 	 * that we can reset the context during error recovery and thereby avoid
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 05637344363..7d2999d77fd 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1632,7 +1632,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, PG_SIG_IGN);
 	pqsignal(SIGPIPE, PG_SIG_IGN);
-	pqsignal(SIGCHLD, PG_SIG_DFL);
 
 	check_and_set_sync_info(MyProcPid);
 
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 05e2f690fa7..4319cb6fd43 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -256,9 +256,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, PG_SIG_IGN);
 
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
-
 	/* Load the libpq-specific functions */
 	load_file("libpqwalreceiver", false);
 	if (WalReceiverFunctions == NULL)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c931d9b4fa8..795393b0be0 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -3977,9 +3977,6 @@ WalSndSignals(void)
 	pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 	pqsignal(SIGUSR2, WalSndLastCycleHandler);	/* request a last cycle and
 												 * shutdown */
-
-	/* Reset some signals that are accepted by postmaster but not here */
-	pqsignal(SIGCHLD, PG_SIG_DFL);
 }
 
 /* Register shared-memory space needed by walsender */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index dbef734a93f..1f2fc3cce9b 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -4332,13 +4332,6 @@ PostgresMain(const char *dbname, const char *username)
 		pqsignal(SIGUSR1, procsignal_sigusr1_handler);
 		pqsignal(SIGUSR2, PG_SIG_IGN);
 		pqsignal(SIGFPE, FloatExceptionHandler);
-
-		/*
-		 * Reset some signals that are accepted by postmaster but not by
-		 * backend
-		 */
-		pqsignal(SIGCHLD, PG_SIG_DFL);	/* system() requires this on some
-										 * platforms */
 	}
 
 	/* Early initialization */
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 7ffc808073a..5343d4219ae 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -143,6 +143,15 @@ InitPostmasterChild(void)
 		elog(FATAL, "setsid() failed: %m");
 #endif
 
+	/*
+	 * 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 */
+
 	/*
 	 * Every postmaster child process is expected to respond promptly to
 	 * SIGQUIT at all times.  Therefore we centrally remove SIGQUIT from
@@ -155,6 +164,11 @@ InitPostmasterChild(void)
 	sigdelset(&BlockSig, SIGQUIT);
 	sigprocmask(SIG_SETMASK, &BlockSig, NULL);
 
+	/*
+	 * It is up to the *Main() function to set signal handlers appropriate for
+	 * the child process and unblock the rest of the signals.
+	 */
+
 	/* Request a signal if the postmaster dies, if possible. */
 	PostmasterDeathSignalInit();
 
-- 
2.47.3



  [text/x-patch] v13-0003-Refactor-datachecksums-launcher-worker-comms-to-.patch (5.4K, ../../[email protected]/4-v13-0003-Refactor-datachecksums-launcher-worker-comms-to-.patch)
  download | inline diff:
From 42c0d060f5713111d4f71bd370b54d440608d5fd Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 23 Jun 2026 20:58:22 +0300
Subject: [PATCH v13 3/9] Refactor datachecksums launcher/worker comms to use
 procnumber

I'm trying to eliminate using PIDs in preparation of the next patches
---
 src/backend/postmaster/datachecksum_state.c | 48 ++++++++++++++-------
 1 file changed, 33 insertions(+), 15 deletions(-)

diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 68557c16cb9..47b87f51ad9 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -209,6 +209,7 @@
 #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"
@@ -326,7 +327,9 @@ typedef struct DataChecksumsStateStruct
 	 */
 	uint64		worker_invocation;	/* unique invocation number */
 	Oid			database_oid;	/* database it's processing */
-	pid_t		worker_pid;		/* worker process's PID */
+
+	/* Worker process's proc number, set by the worker when it starts up */
+	ProcNumber	worker_procno;
 
 	/*
 	 * These fields indicate the target state that the worker is currently
@@ -840,7 +843,7 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 	 * if it completes successfully.
 	 */
 	DataChecksumState->worker_result = DATACHECKSUMSWORKER_FAILED;
-	DataChecksumState->worker_pid = InvalidPid;
+	DataChecksumState->worker_procno = INVALID_PROC_NUMBER;
 
 	invocation = ++DataChecksumState->worker_invocation_counter;
 	DataChecksumState->worker_invocation = invocation;
@@ -883,6 +886,7 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 		 */
 		LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
 		Assert(DataChecksumState->worker_invocation == invocation);
+		Assert(DataChecksumState->worker_procno == INVALID_PROC_NUMBER);
 		if (DataChecksumState->worker_result == DATACHECKSUMSWORKER_SUCCESSFUL)
 		{
 			LWLockRelease(DataChecksumsWorkerLock);
@@ -924,12 +928,6 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 			errmsg("initiating data checksum processing in database \"%s\"",
 				   db->dbname));
 
-	/* Save the pid of the worker so we can signal it later */
-	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
-	Assert(DataChecksumState->worker_invocation == invocation);
-	DataChecksumState->worker_pid = pid;
-	LWLockRelease(DataChecksumsWorkerLock);
-
 	snprintf(activity, sizeof(activity) - 1,
 			 "Waiting for worker in database %s (pid %ld)", db->dbname, (long) pid);
 	pgstat_report_activity(STATE_RUNNING, activity);
@@ -942,10 +940,10 @@ ProcessDatabase(DataChecksumsWorkerDatabase *db)
 					   db->dbname),
 				errhint("Restart the database and restart data checksum processing by calling pg_enable_data_checksums()."));
 
-	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
+	LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
 	Assert(DataChecksumState->worker_invocation == invocation);
+	Assert(DataChecksumState->worker_procno == INVALID_PROC_NUMBER);
 	result = DataChecksumState->worker_result;
-	DataChecksumState->worker_pid = InvalidPid;
 	LWLockRelease(DataChecksumsWorkerLock);
 
 	if (result == DATACHECKSUMSWORKER_ABORTED)
@@ -974,12 +972,11 @@ launcher_exit(int code, Datum arg)
 	if (launcher_running)
 	{
 		LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
-		if (DataChecksumState->worker_pid != InvalidPid)
+		if (DataChecksumState->worker_procno != INVALID_PROC_NUMBER)
 		{
 			ereport(LOG,
 					errmsg("data checksums launcher exiting while worker is still running, signalling worker"));
-			kill(DataChecksumState->worker_pid, SIGTERM);
-			DataChecksumState->worker_pid = InvalidPid;
+			kill(GetPGProcByNumber(DataChecksumState->worker_procno)->pid, SIGTERM);
 		}
 		LWLockRelease(DataChecksumsWorkerLock);
 	}
@@ -1092,7 +1089,6 @@ WaitForAllTransactionsToFinish(void)
 void
 DataChecksumsWorkerLauncherMain(Datum arg)
 {
-
 	ereport(DEBUG1,
 			errmsg("background worker \"datachecksums launcher\" started"));
 
@@ -1529,6 +1525,21 @@ BuildRelationList(bool temp_relations, bool include_shared)
 	return RelationList;
 }
 
+/*
+ * worker_exit
+ *
+ * Internal routine for cleaning up state when a worker process exits due to
+ * an error.
+ */
+static void
+worker_exit(int code, Datum arg)
+{
+	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
+	if (DataChecksumState->worker_invocation == worker_invocation)
+		DataChecksumState->worker_procno = INVALID_PROC_NUMBER;
+	LWLockRelease(DataChecksumsWorkerLock);
+}
+
 /*
  * DataChecksumsWorkerMain
  *
@@ -1566,15 +1577,22 @@ DataChecksumsWorkerMain(Datum arg)
 	MyBackendType = B_DATACHECKSUMSWORKER_WORKER;
 	init_ps_display(NULL);
 
-	LWLockAcquire(DataChecksumsWorkerLock, LW_SHARED);
+	LWLockAcquire(DataChecksumsWorkerLock, LW_EXCLUSIVE);
 	if (DataChecksumState->worker_invocation != worker_invocation)
 	{
 		LWLockRelease(DataChecksumsWorkerLock);
 		return;
 	}
+	if (DataChecksumState->worker_procno != INVALID_PROC_NUMBER)
+		elog(ERROR, "data checksums background worker is already running");
+	DataChecksumState->worker_procno = MyProcNumber;
+
 	dboid = DataChecksumState->database_oid;
 	LWLockRelease(DataChecksumsWorkerLock);
 
+	/* Arrange to clear worker_procno on exit */
+	on_shmem_exit(worker_exit, 0);
+
 	BackgroundWorkerInitializeConnectionByOid(dboid, InvalidOid,
 											  BGWORKER_BYPASS_ALLOWCONN);
 
-- 
2.47.3



  [text/x-patch] v13-0004-Refactor-PARALLEL_MESSAGE-procsignal-signaling.patch (15.0K, ../../[email protected]/5-v13-0004-Refactor-PARALLEL_MESSAGE-procsignal-signaling.patch)
  download | inline diff:
From 6a82771fbab938cd10e14de7490341c3b546dfc6 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 16 Jun 2026 22:06:03 +0300
Subject: [PATCH v13 4/9] Refactor PARALLEL_MESSAGE procsignal signaling

When a message is sent to a shm_mq in pq_putmessage(), the sender
sends a PROCSIG_PARALLEL_MESSAGE procsignal to the receiver, to notify
it of the new message. However, in case of a parallel apply worker or
a repack worker, we used PROCSIG_PARALLEL_APPLY_MESSAGE or
PROCSIG_REPACK_MESSAGE instead. Remove those extra procsignals, always
use PROCSIG_PARALLEL_MESSAGE, and move the logic to the receiving side
instead to check all different kinds of incoming messages.

This deduplicates the code between ProcessParallelMessages(),
ProcessParallelApplyMessages(), and ProcessRepackMessages() to set up
the short-lived memory context and to hold interrupts.
---
 src/backend/access/transam/parallel.c         | 41 +++++++++---
 src/backend/commands/repack.c                 | 60 +-----------------
 src/backend/commands/repack_worker.c          |  2 +-
 src/backend/libpq/pqmq.c                      | 20 +-----
 .../replication/logical/applyparallelworker.c | 63 ++-----------------
 src/backend/storage/ipc/procsignal.c          |  6 --
 src/backend/tcop/postgres.c                   |  8 +--
 src/include/access/parallel.h                 |  2 +-
 src/include/replication/logicalworker.h       |  1 -
 src/include/storage/procsignal.h              |  2 -
 10 files changed, 46 insertions(+), 159 deletions(-)

diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 89e9d224eec..5c92e05ad86 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_enum.h"
 #include "catalog/storage.h"
 #include "commands/async.h"
+#include "commands/repack.h"
 #include "commands/vacuum.h"
 #include "executor/execParallel.h"
 #include "libpq/libpq.h"
@@ -34,6 +35,7 @@
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
+#include "replication/logicalworker.h"
 #include "storage/ipc.h"
 #include "storage/predicate.h"
 #include "storage/proc.h"
@@ -160,6 +162,7 @@ static const struct
 };
 
 /* Private functions. */
+static void ProcessParallelMessages(void);
 static void ProcessParallelMessage(ParallelContext *pcxt, int i, StringInfo msg);
 static void WaitForParallelWorkersToExit(ParallelContext *pcxt);
 static parallel_worker_main_type LookupParallelWorkerFunction(const char *libraryname, const char *funcname);
@@ -1054,9 +1057,8 @@ HandleParallelMessageInterrupt(void)
  * Process any queued protocol messages received from parallel workers.
  */
 void
-ProcessParallelMessages(void)
+ProcessParallelMessageInterrupt(void)
 {
-	dlist_iter	iter;
 	MemoryContext oldcontext;
 
 	static MemoryContext hpm_context = NULL;
@@ -1087,6 +1089,34 @@ ProcessParallelMessages(void)
 	/* OK to process messages.  Reset the flag saying there are more to do. */
 	ParallelMessagePending = false;
 
+	/* Process messages from parallel query workers */
+	ProcessParallelMessages();
+
+	/* Process messages from REPACK CONCURRENTLY workers */
+	ProcessRepackMessages();
+
+	/* Process messages from replication parallel apply workers */
+	ProcessParallelApplyMessages();
+
+	MemoryContextSwitchTo(oldcontext);
+
+	/* Might as well clear the context on our way out */
+	MemoryContextReset(hpm_context);
+
+	RESUME_INTERRUPTS();
+}
+
+/*
+ * Process any incoming messages from parallel workers.
+ *
+ * This is called from CHECK_FOR_INTERRUPTS(), but in a short-lived memory
+ * context.
+ */
+static void
+ProcessParallelMessages(void)
+{
+	dlist_iter	iter;
+
 	dlist_foreach(iter, &pcxt_list)
 	{
 		ParallelContext *pcxt;
@@ -1130,13 +1160,6 @@ ProcessParallelMessages(void)
 			}
 		}
 	}
-
-	MemoryContextSwitchTo(oldcontext);
-
-	/* Might as well clear the context on our way out */
-	MemoryContextReset(hpm_context);
-
-	RESUME_INTERRUPTS();
 }
 
 /*
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 4d177c868bb..a2a4c286772 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -145,12 +145,6 @@ typedef struct DecodingWorker
 /* Pointer to currently running decoding worker. */
 static DecodingWorker *decoding_worker = NULL;
 
-/*
- * Is there a message sent by a repack worker that the backend needs to
- * receive?
- */
-volatile sig_atomic_t RepackMessagePending = false;
-
 static LOCKMODE RepackLockLevel(bool concurrent);
 static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap,
 								Oid indexOid, Oid userid, LOCKMODE lmode,
@@ -3647,30 +3641,15 @@ DecodingWorkerFileName(char *fname, Oid relid, uint32 seq)
 	snprintf(fname, MAXPGPATH, "%u-%u", relid, seq);
 }
 
-/*
- * Handle receipt of an interrupt indicating a repack 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
- * ProcessRepackMessages().
- */
-void
-HandleRepackMessageInterrupt(void)
-{
-	InterruptPending = true;
-	RepackMessagePending = true;
-	SetLatch(MyLatch);
-}
-
 /*
  * Process any queued protocol messages received from the repack worker.
+ *
+ * This is called from CHECK_FOR_INTERRUPTS(), but in a short-lived memory
+ * context.
  */
 void
 ProcessRepackMessages(void)
 {
-	MemoryContext oldcontext;
-	static MemoryContext hpm_context = NULL;
-
 	/*
 	 * Nothing to do if we haven't launched the worker yet or have already
 	 * terminated it.
@@ -3678,32 +3657,6 @@ ProcessRepackMessages(void)
 	if (decoding_worker == NULL)
 		return;
 
-	/*
-	 * This is invoked from ProcessInterrupts(), and since some of the
-	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
-	 * for recursive calls if more signals are received while this runs.  It's
-	 * unclear that recursive entry would be safe, and it doesn't seem useful
-	 * even if it is safe, so let's block interrupts until done.
-	 */
-	HOLD_INTERRUPTS();
-
-	/*
-	 * Moreover, CurrentMemoryContext might be pointing almost anywhere.  We
-	 * don't want to risk leaking data into long-lived contexts, so let's do
-	 * our work here in a private context that we can reset on each use.
-	 */
-	if (hpm_context == NULL)	/* first time through? */
-		hpm_context = AllocSetContextCreate(TopMemoryContext,
-											"ProcessRepackMessages",
-											ALLOCSET_DEFAULT_SIZES);
-	else
-		MemoryContextReset(hpm_context);
-
-	oldcontext = MemoryContextSwitchTo(hpm_context);
-
-	/* OK to process messages.  Reset the flag saying there are more to do. */
-	RepackMessagePending = false;
-
 	/*
 	 * Read as many messages as we can from the worker, but stop when no more
 	 * messages can be read from the worker without blocking.
@@ -3738,13 +3691,6 @@ ProcessRepackMessages(void)
 			break;
 		}
 	}
-
-	MemoryContextSwitchTo(oldcontext);
-
-	/* Might as well clear the context on our way out */
-	MemoryContextReset(hpm_context);
-
-	RESUME_INTERRUPTS();
 }
 
 /*
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index db9ff057cc6..9ff2e14904c 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -172,7 +172,7 @@ RepackWorkerShutdown(int code, Datum arg)
 	DecodingWorkerShared *shared = (DecodingWorkerShared *) DatumGetPointer(arg);
 
 	SendProcSignal(shared->backend_pid,
-				   PROCSIG_REPACK_MESSAGE,
+				   PROCSIG_PARALLEL_MESSAGE,
 				   shared->backend_proc_number);
 
 	dsm_detach(worker_dsm_segment);
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index d038a9da515..238fb73ebd8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -172,23 +172,9 @@ mq_putmessage(char msgtype, const char *s, size_t len)
 		result = shm_mq_sendv(pq_mq_handle, iov, 2, true, true);
 
 		if (pq_mq_parallel_leader_pid != 0)
-		{
-			if (IsLogicalParallelApplyWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_APPLY_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else if (AmRepackWorker())
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_REPACK_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			else
-			{
-				Assert(IsParallelWorker());
-				SendProcSignal(pq_mq_parallel_leader_pid,
-							   PROCSIG_PARALLEL_MESSAGE,
-							   pq_mq_parallel_leader_proc_number);
-			}
-		}
+			SendProcSignal(pq_mq_parallel_leader_pid,
+						   PROCSIG_PARALLEL_MESSAGE,
+						   pq_mq_parallel_leader_proc_number);
 
 		if (result != SHM_MQ_WOULD_BLOCK)
 			break;
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 012d55e9d3d..9535abef122 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -241,12 +241,6 @@ static List *ParallelApplyWorkerPool = NIL;
  */
 ParallelApplyWorkerShared *MyParallelShared = NULL;
 
-/*
- * Is there a message sent by a parallel apply worker that the leader apply
- * worker needs to receive?
- */
-volatile sig_atomic_t ParallelApplyMessagePending = false;
-
 /*
  * Cache the parallel apply worker information required for applying the
  * current streaming transaction. It is used to save the cost of searching the
@@ -857,7 +851,7 @@ static void
 pa_shutdown(int code, Datum arg)
 {
 	SendProcSignal(MyLogicalRepWorker->leader_pid,
-				   PROCSIG_PARALLEL_APPLY_MESSAGE,
+				   PROCSIG_PARALLEL_MESSAGE,
 				   INVALID_PROC_NUMBER);
 
 	dsm_detach((dsm_segment *) DatumGetPointer(arg));
@@ -997,21 +991,6 @@ ParallelApplyWorkerMain(Datum main_arg)
 	Assert(false);
 }
 
-/*
- * Handle receipt of an interrupt indicating a parallel apply worker message.
- *
- * Note: this is called within a signal handler! All we can do is set a flag
- * that will cause the next CHECK_FOR_INTERRUPTS() to invoke
- * ProcessParallelApplyMessages().
- */
-void
-HandleParallelApplyMessageInterrupt(void)
-{
-	InterruptPending = true;
-	ParallelApplyMessagePending = true;
-	/* latch will be set by procsignal_sigusr1_handler */
-}
-
 /*
  * Process a single protocol message received from a single parallel apply
  * worker.
@@ -1076,40 +1055,15 @@ ProcessParallelApplyMessage(StringInfo msg)
 }
 
 /*
- * Handle any queued protocol messages received from parallel apply workers.
+ * Process any queued protocol messages received from parallel apply workers.
+ *
+ * This is called from CHECK_FOR_INTERRUPTS(), but in a short-lived memory
+ * context.
  */
 void
 ProcessParallelApplyMessages(void)
 {
 	ListCell   *lc;
-	MemoryContext oldcontext;
-
-	static MemoryContext hpam_context = NULL;
-
-	/*
-	 * This is invoked from ProcessInterrupts(), and since some of the
-	 * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential
-	 * for recursive calls if more signals are received while this runs. It's
-	 * unclear that recursive entry would be safe, and it doesn't seem useful
-	 * even if it is safe, so let's block interrupts until done.
-	 */
-	HOLD_INTERRUPTS();
-
-	/*
-	 * Moreover, CurrentMemoryContext might be pointing almost anywhere. We
-	 * don't want to risk leaking data into long-lived contexts, so let's do
-	 * our work here in a private context that we can reset on each use.
-	 */
-	if (!hpam_context)			/* first time through? */
-		hpam_context = AllocSetContextCreate(TopMemoryContext,
-											 "ProcessParallelApplyMessages",
-											 ALLOCSET_DEFAULT_SIZES);
-	else
-		MemoryContextReset(hpam_context);
-
-	oldcontext = MemoryContextSwitchTo(hpam_context);
-
-	ParallelApplyMessagePending = false;
 
 	foreach(lc, ParallelApplyWorkerPool)
 	{
@@ -1145,13 +1099,6 @@ ProcessParallelApplyMessages(void)
 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
 					 errmsg("lost connection to the logical replication parallel apply worker")));
 	}
-
-	MemoryContextSwitchTo(oldcontext);
-
-	/* Might as well clear the context on our way out */
-	MemoryContextReset(hpam_context);
-
-	RESUME_INTERRUPTS();
 }
 
 /*
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 1397f65f67b..00b640f2d53 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -713,12 +713,6 @@ procsignal_sigusr1_handler(SIGNAL_ARGS)
 	if (CheckProcSignal(PROCSIG_LOG_MEMORY_CONTEXT))
 		HandleLogMemoryContextInterrupt();
 
-	if (CheckProcSignal(PROCSIG_PARALLEL_APPLY_MESSAGE))
-		HandleParallelApplyMessageInterrupt();
-
-	if (CheckProcSignal(PROCSIG_REPACK_MESSAGE))
-		HandleRepackMessageInterrupt();
-
 	if (CheckProcSignal(PROCSIG_SLOTSYNC_MESSAGE))
 		HandleSlotSyncMessageInterrupt();
 
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 1f2fc3cce9b..80449b0b3cd 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3604,19 +3604,13 @@ ProcessInterrupts(void)
 		ProcessProcSignalBarrier();
 
 	if (ParallelMessagePending)
-		ProcessParallelMessages();
+		ProcessParallelMessageInterrupt();
 
 	if (LogMemoryContextPending)
 		ProcessLogMemoryContextInterrupt();
 
-	if (ParallelApplyMessagePending)
-		ProcessParallelApplyMessages();
-
 	if (SlotSyncShutdownPending)
 		ProcessSlotSyncMessage();
-
-	if (RepackMessagePending)
-		ProcessRepackMessages();
 }
 
 /*
diff --git a/src/include/access/parallel.h b/src/include/access/parallel.h
index 60f857675e0..0ed0a88d831 100644
--- a/src/include/access/parallel.h
+++ b/src/include/access/parallel.h
@@ -73,7 +73,7 @@ extern void DestroyParallelContext(ParallelContext *pcxt);
 extern bool ParallelContextActive(void);
 
 extern void HandleParallelMessageInterrupt(void);
-extern void ProcessParallelMessages(void);
+extern void ProcessParallelMessageInterrupt(void);
 extern void AtEOXact_Parallel(bool isCommit);
 extern void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId);
 extern void ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end);
diff --git a/src/include/replication/logicalworker.h b/src/include/replication/logicalworker.h
index 7d748a28da8..f6994e9a1a1 100644
--- a/src/include/replication/logicalworker.h
+++ b/src/include/replication/logicalworker.h
@@ -24,7 +24,6 @@ extern void SequenceSyncWorkerMain(Datum main_arg);
 extern bool IsLogicalWorker(void);
 extern bool IsLogicalParallelApplyWorker(void);
 
-extern void HandleParallelApplyMessageInterrupt(void);
 extern void ProcessParallelApplyMessages(void);
 
 extern void LogicalRepWorkersWakeupAtCommit(Oid subid);
diff --git a/src/include/storage/procsignal.h b/src/include/storage/procsignal.h
index aaa158bfd66..691314e26bf 100644
--- a/src/include/storage/procsignal.h
+++ b/src/include/storage/procsignal.h
@@ -35,9 +35,7 @@ typedef enum
 	PROCSIG_WALSND_INIT_STOPPING,	/* ask walsenders to prepare for shutdown  */
 	PROCSIG_BARRIER,			/* global barrier interrupt  */
 	PROCSIG_LOG_MEMORY_CONTEXT, /* ask backend to log the memory contexts */
-	PROCSIG_PARALLEL_APPLY_MESSAGE, /* Message from parallel apply workers */
 	PROCSIG_SLOTSYNC_MESSAGE,	/* ask slot synchronization to stop */
-	PROCSIG_REPACK_MESSAGE,		/* Message from repack worker */
 	PROCSIG_RECOVERY_CONFLICT,	/* backend is blocking recovery, check
 								 * PGPROC->pendingRecoveryConflicts for the
 								 * reason */
-- 
2.47.3



  [text/x-patch] v13-0005-Move-CHECK_FOR_INTERRUPTS-and-friends-to-new-ipc.patch (116.7K, ../../[email protected]/6-v13-0005-Move-CHECK_FOR_INTERRUPTS-and-friends-to-new-ipc.patch)
  download | inline diff:
From b6dd24fec67f5e9a4e26ecb4fe7bb7487bafaf31 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 24 Jun 2026 19:07:18 +0300
Subject: [PATCH v13 5/9] Move CHECK_FOR_INTERRUPTS() and friends to new
 ipc/interrupt.h header

---
 contrib/amcheck/verify_gin.c                  |   1 +
 contrib/amcheck/verify_heapam.c               |   2 +-
 contrib/amcheck/verify_nbtree.c               |   2 +-
 contrib/bloom/blinsert.c                      |   2 +-
 contrib/file_fdw/file_fdw.c                   |   1 +
 contrib/hstore_plperl/hstore_plperl.c         |   2 +-
 contrib/jsonb_plperl/jsonb_plperl.c           |   1 +
 contrib/ltree/lquery_op.c                     |   1 +
 contrib/pg_prewarm/pg_prewarm.c               |   1 +
 contrib/pg_surgery/heap_surgery.c             |   1 +
 contrib/pg_trgm/trgm_op.c                     |   2 +-
 contrib/pg_visibility/pg_visibility.c         |   2 +-
 contrib/pg_walinspect/pg_walinspect.c         |   1 +
 contrib/pgcrypto/crypt-blowfish.c             |   3 +-
 contrib/pgcrypto/crypt-des.c                  |   3 +-
 contrib/pgcrypto/crypt-sha.c                  |   2 +-
 contrib/pgstattuple/pgstatapprox.c            |   1 +
 contrib/pgstattuple/pgstatindex.c             |   1 +
 contrib/pgstattuple/pgstattuple.c             |   1 +
 contrib/postgres_fdw/connection.c             |   1 +
 contrib/postgres_fdw/postgres_fdw.c           |   1 +
 contrib/tsm_system_rows/tsm_system_rows.c     |   2 +-
 contrib/tsm_system_time/tsm_system_time.c     |   2 +-
 src/backend/access/brin/brin.c                |   2 +-
 src/backend/access/brin/brin_pageops.c        |   2 +-
 src/backend/access/brin/brin_revmap.c         |   2 +-
 src/backend/access/common/toast_internals.c   |   2 +-
 src/backend/access/gin/ginbtree.c             |   2 +-
 src/backend/access/gin/gindatapage.c          |   2 +-
 src/backend/access/gin/ginfast.c              |   1 +
 src/backend/access/gin/ginget.c               |   1 +
 src/backend/access/gin/gininsert.c            |   2 +-
 src/backend/access/gin/ginutil.c              |   2 +-
 src/backend/access/gin/ginvacuum.c            |   1 +
 src/backend/access/gist/gist.c                |   1 +
 src/backend/access/gist/gistbuild.c           |   1 +
 src/backend/access/gist/gistget.c             |   2 +-
 src/backend/access/gist/gistvacuum.c          |   2 +-
 src/backend/access/hash/hash.c                |   2 +-
 src/backend/access/hash/hashinsert.c          |   2 +-
 src/backend/access/hash/hashovfl.c            |   2 +-
 src/backend/access/hash/hashpage.c            |   2 +-
 src/backend/access/hash/hashsearch.c          |   2 +-
 src/backend/access/hash/hashsort.c            |   2 +-
 src/backend/access/heap/heapam_handler.c      |   2 +-
 src/backend/access/heap/pruneheap.c           |   2 +-
 src/backend/access/heap/vacuumlazy.c          |   2 +-
 src/backend/access/heap/visibilitymap.c       |   2 +-
 src/backend/access/index/genam.c              |   1 +
 src/backend/access/nbtree/nbtdedup.c          |   2 +-
 src/backend/access/nbtree/nbtinsert.c         |   2 +-
 src/backend/access/nbtree/nbtpage.c           |   1 +
 src/backend/access/nbtree/nbtsearch.c         |   2 +-
 src/backend/access/nbtree/nbtsort.c           |   2 +-
 src/backend/access/spgist/spgdoinsert.c       |   2 +-
 src/backend/access/spgist/spginsert.c         |   2 +-
 src/backend/access/spgist/spgscan.c           |   2 +-
 src/backend/access/spgist/spgvacuum.c         |   2 +-
 src/backend/access/transam/generic_xlog.c     |   2 +-
 src/backend/access/transam/multixact.c        |   2 +-
 src/backend/access/transam/slru.c             |   2 +-
 src/backend/access/transam/twophase.c         |   2 +-
 src/backend/access/transam/xlogfuncs.c        |   2 +-
 src/backend/access/transam/xlogutils.c        |   2 +-
 src/backend/access/transam/xlogwait.c         |   2 +-
 src/backend/backup/basebackup.c               |   2 +-
 src/backend/backup/basebackup_throttle.c      |   2 +-
 src/backend/backup/walsummaryfuncs.c          |   2 +-
 src/backend/bootstrap/bootparse.y             |   2 +-
 src/backend/catalog/catalog.c                 |   1 +
 src/backend/catalog/index.c                   |   2 +-
 src/backend/catalog/storage.c                 |   2 +-
 src/backend/commands/analyze.c                |   2 +-
 src/backend/commands/copyfrom.c               |   2 +-
 src/backend/commands/dbcommands.c             |   2 +-
 src/backend/commands/indexcmds.c              |   2 +-
 src/backend/commands/matview.c                |   2 +-
 src/backend/commands/publicationcmds.c        |   1 +
 src/backend/commands/repack.c                 |   2 +-
 src/backend/commands/repack_worker.c          |   1 +
 src/backend/commands/sequence.c               |   2 +-
 src/backend/commands/vacuum.c                 |   2 +-
 src/backend/executor/execPartition.c          |   1 +
 src/backend/executor/execProcnode.c           |   1 +
 src/backend/executor/execSRF.c                |   2 +-
 src/backend/executor/nodeAgg.c                |   1 +
 src/backend/executor/nodeAppend.c             |   2 +-
 src/backend/executor/nodeBitmapHeapscan.c     |   2 +-
 src/backend/executor/nodeBitmapIndexscan.c    |   1 +
 src/backend/executor/nodeCustom.c             |   2 +-
 src/backend/executor/nodeGather.c             |   1 +
 src/backend/executor/nodeGatherMerge.c        |   2 +-
 src/backend/executor/nodeGroup.c              |   2 +-
 src/backend/executor/nodeHash.c               |   1 +
 src/backend/executor/nodeHashjoin.c           |   2 +-
 src/backend/executor/nodeIncrementalSort.c    |   1 +
 src/backend/executor/nodeIndexonlyscan.c      |   2 +-
 src/backend/executor/nodeIndexscan.c          |   2 +-
 src/backend/executor/nodeLimit.c              |   2 +-
 src/backend/executor/nodeLockRows.c           |   2 +-
 src/backend/executor/nodeMaterial.c           |   1 +
 src/backend/executor/nodeMemoize.c            |   1 +
 src/backend/executor/nodeMergeAppend.c        |   2 +-
 src/backend/executor/nodeMergejoin.c          |   2 +-
 src/backend/executor/nodeModifyTable.c        |   2 +-
 src/backend/executor/nodeNestloop.c           |   2 +-
 src/backend/executor/nodeProjectSet.c         |   2 +-
 src/backend/executor/nodeRecursiveunion.c     |   1 +
 src/backend/executor/nodeResult.c             |   2 +-
 src/backend/executor/nodeSetOp.c              |   2 +-
 src/backend/executor/nodeSort.c               |   1 +
 src/backend/executor/nodeSubplan.c            |   2 +-
 src/backend/executor/nodeTableFuncscan.c      |   1 +
 src/backend/executor/nodeTidscan.c            |   2 +-
 src/backend/executor/nodeUnique.c             |   2 +-
 src/backend/executor/nodeWindowAgg.c          |   1 +
 src/backend/lib/bipartite_match.c             |   1 +
 src/backend/libpq/auth.c                      |   2 +-
 src/backend/libpq/pqcomm.c                    |   2 +-
 src/backend/libpq/pqmq.c                      |   2 +-
 src/backend/optimizer/prep/prepjointree.c     |   1 +
 src/backend/optimizer/util/pathnode.c         |   1 +
 src/backend/optimizer/util/predtest.c         |   2 +-
 src/backend/partitioning/partbounds.c         |   2 +-
 src/backend/port/win32_sema.c                 |   1 +
 src/backend/postmaster/auxprocess.c           |   2 +-
 src/backend/postmaster/bgwriter.c             |   2 +-
 src/backend/postmaster/checkpointer.c         |   2 +-
 src/backend/postmaster/datachecksum_state.c   |   2 +-
 src/backend/postmaster/interrupt.c            |   1 +
 src/backend/postmaster/pgarch.c               |   1 +
 src/backend/postmaster/startup.c              |   1 +
 src/backend/postmaster/walsummarizer.c        |   2 +-
 src/backend/postmaster/walwriter.c            |   2 +-
 .../libpqwalreceiver/libpqwalreceiver.c       |   3 +-
 .../replication/logical/applyparallelworker.c |   1 +
 src/backend/replication/logical/launcher.c    |   2 +-
 src/backend/replication/logical/logical.c     |   2 +-
 src/backend/replication/logical/logicalctl.c  |   2 +-
 .../replication/logical/logicalfuncs.c        |   2 +-
 src/backend/replication/logical/origin.c      |   2 +-
 .../replication/logical/reorderbuffer.c       |   2 +-
 .../replication/logical/sequencesync.c        |   1 +
 src/backend/replication/logical/slotsync.c    |   1 +
 src/backend/replication/logical/tablesync.c   |   2 +-
 src/backend/replication/slot.c                |   2 +-
 src/backend/replication/syncrep.c             |   2 +-
 src/backend/replication/walreceiver.c         |   2 +-
 src/backend/storage/aio/aio.c                 |   1 +
 src/backend/storage/aio/aio_callback.c        |   2 +-
 src/backend/storage/aio/aio_io.c              |   2 +-
 src/backend/storage/aio/method_io_uring.c     |   2 +-
 src/backend/storage/aio/method_worker.c       |   2 +-
 src/backend/storage/file/buffile.c            |   2 +-
 src/backend/storage/file/copydir.c            |   2 +-
 src/backend/storage/file/fd.c                 |   2 +-
 src/backend/storage/freespace/freespace.c     |   2 +-
 src/backend/storage/ipc/dsm.c                 |   1 +
 src/backend/storage/ipc/ipc.c                 |   1 +
 src/backend/storage/ipc/procsignal.c          |   2 +-
 src/backend/storage/ipc/shm_mq.c              |   2 +-
 src/backend/storage/ipc/shmem.c               |   1 +
 src/backend/storage/ipc/signalfuncs.c         |   2 +-
 src/backend/storage/ipc/standby.c             |   2 +-
 src/backend/storage/lmgr/condition_variable.c |   2 +-
 src/backend/storage/lmgr/lmgr.c               |   2 +-
 src/backend/storage/lmgr/lock.c               |   2 +-
 src/backend/storage/lmgr/lwlock.c             |   2 +-
 src/backend/storage/lmgr/proc.c               |   2 +-
 src/backend/storage/page/bufpage.c            |   1 +
 src/backend/storage/smgr/smgr.c               |   2 +-
 src/backend/tcop/fastpath.c                   |   1 +
 src/backend/tcop/pquery.c                     |   2 +-
 src/backend/tsearch/wparser_def.c             |   1 +
 src/backend/utils/adt/dbsize.c                |   1 +
 src/backend/utils/adt/geo_ops.c               |   1 +
 src/backend/utils/adt/jsonpath.c              |   1 +
 src/backend/utils/adt/jsonpath_exec.c         |   1 +
 src/backend/utils/adt/jsonpath_gram.y         |   2 +-
 src/backend/utils/adt/like.c                  |   1 +
 src/backend/utils/adt/numeric.c               |   2 +-
 src/backend/utils/adt/oracle_compat.c         |   2 +-
 src/backend/utils/adt/orderedsetaggs.c        |   1 +
 src/backend/utils/adt/ruleutils.c             |   1 +
 src/backend/utils/adt/tsquery_rewrite.c       |   1 +
 src/backend/utils/adt/tsvector_op.c           |   1 +
 src/backend/utils/adt/varlena.c               |   2 +-
 src/backend/utils/cache/inval.c               |   1 +
 src/backend/utils/init/globals.c              |   1 +
 src/backend/utils/misc/timeout.c              |   1 +
 src/backend/utils/mmgr/mcxt.c                 |   1 +
 src/backend/utils/sort/qsort_interruptible.c  |   2 +-
 src/backend/utils/sort/tuplesort.c            |   2 +-
 src/backend/utils/sort/tuplestore.c           |   2 +-
 src/backend/utils/time/combocid.c             |   2 +-
 src/common/scram-common.c                     |   2 +-
 src/include/executor/execScan.h               |   2 +-
 src/include/ipc/interrupt.h                   | 192 ++++++++++++++++++
 src/include/libpq/libpq-be-fe-helpers.h       |   3 +-
 src/include/miscadmin.h                       | 128 ------------
 src/include/regex/regcustom.h                 |   1 +
 src/include/utils/backend_status.h            |   1 +
 src/include/utils/pgstat_internal.h           |   1 +
 src/pl/plperl/plperl.c                        |   1 +
 src/pl/plpgsql/src/pl_exec.c                  |   2 +-
 src/port/pg_numa.c                            |   5 +-
 .../injection_points/regress_injection.c      |   2 +-
 .../modules/test_bitmapset/test_bitmapset.c   |   2 +-
 .../test_bloomfilter/test_bloomfilter.c       |   2 +-
 .../modules/test_saslprep/test_saslprep.c     |   2 +-
 src/test/modules/test_shm_mq/setup.c          |   1 +
 src/test/modules/test_shm_mq/test.c           |   2 +-
 src/test/modules/test_shm_mq/worker.c         |   2 +-
 .../modules/xid_wraparound/xid_wraparound.c   |   2 +-
 src/test/regress/regress.c                    |   2 +-
 215 files changed, 411 insertions(+), 272 deletions(-)
 create mode 100644 src/include/ipc/interrupt.h

diff --git a/contrib/amcheck/verify_gin.c b/contrib/amcheck/verify_gin.c
index abfad07d5e4..319c1008c4d 100644
--- a/contrib/amcheck/verify_gin.c
+++ b/contrib/amcheck/verify_gin.c
@@ -25,6 +25,7 @@
 #include "access/gin_private.h"
 #include "access/nbtree.h"
 #include "catalog/pg_am.h"
+#include "ipc/interrupt.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
 #include "verify_common.h"
diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c
index 20ff58aa782..d3b1e5787f9 100644
--- a/contrib/amcheck/verify_heapam.c
+++ b/contrib/amcheck/verify_heapam.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_class.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lwlock.h"
 #include "storage/procarray.h"
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 3ef2d66f826..4ff2dfed618 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -35,8 +35,8 @@
 #include "catalog/pg_am.h"
 #include "catalog/pg_opfamily_d.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/bloomfilter.h"
-#include "miscadmin.h"
 #include "storage/smgr.h"
 #include "utils/guc.h"
 #include "utils/memutils.h"
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index df24856d9ae..5e774798da0 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -16,7 +16,7 @@
 #include "access/generic_xlog.h"
 #include "access/tableam.h"
 #include "bloom.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "utils/memutils.h"
diff --git a/contrib/file_fdw/file_fdw.c b/contrib/file_fdw/file_fdw.c
index 33a37d832ce..f684fd014eb 100644
--- a/contrib/file_fdw/file_fdw.c
+++ b/contrib/file_fdw/file_fdw.c
@@ -30,6 +30,7 @@
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
 #include "foreign/foreign.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "optimizer/optimizer.h"
diff --git a/contrib/hstore_plperl/hstore_plperl.c b/contrib/hstore_plperl/hstore_plperl.c
index 336ead65a18..6a8e4461015 100644
--- a/contrib/hstore_plperl/hstore_plperl.c
+++ b/contrib/hstore_plperl/hstore_plperl.c
@@ -2,7 +2,7 @@
 
 #include "fmgr.h"
 #include "hstore/hstore.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "plperl.h"
 
 PG_MODULE_MAGIC_EXT(
diff --git a/contrib/jsonb_plperl/jsonb_plperl.c b/contrib/jsonb_plperl/jsonb_plperl.c
index 97d147cc65a..c4ea78d3af5 100644
--- a/contrib/jsonb_plperl/jsonb_plperl.c
+++ b/contrib/jsonb_plperl/jsonb_plperl.c
@@ -3,6 +3,7 @@
 #include <math.h>
 
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "plperl.h"
 #include "utils/fmgrprotos.h"
diff --git a/contrib/ltree/lquery_op.c b/contrib/ltree/lquery_op.c
index e6a1969c3d3..c6d1779b799 100644
--- a/contrib/ltree/lquery_op.c
+++ b/contrib/ltree/lquery_op.c
@@ -8,6 +8,7 @@
 #include <ctype.h>
 
 #include "catalog/pg_collation.h"
+#include "ipc/interrupt.h"
 #include "ltree.h"
 #include "miscadmin.h"
 #include "utils/array.h"
diff --git a/contrib/pg_prewarm/pg_prewarm.c b/contrib/pg_prewarm/pg_prewarm.c
index c2716086693..6855bdc6d93 100644
--- a/contrib/pg_prewarm/pg_prewarm.c
+++ b/contrib/pg_prewarm/pg_prewarm.c
@@ -18,6 +18,7 @@
 #include "access/relation.h"
 #include "catalog/index.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
diff --git a/contrib/pg_surgery/heap_surgery.c b/contrib/pg_surgery/heap_surgery.c
index b8ce1095782..1263c7b76ec 100644
--- a/contrib/pg_surgery/heap_surgery.c
+++ b/contrib/pg_surgery/heap_surgery.c
@@ -17,6 +17,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "catalog/pg_am_d.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "utils/acl.h"
diff --git a/contrib/pg_trgm/trgm_op.c b/contrib/pg_trgm/trgm_op.c
index 22bcc3c3361..79634deb1ec 100644
--- a/contrib/pg_trgm/trgm_op.c
+++ b/contrib/pg_trgm/trgm_op.c
@@ -8,8 +8,8 @@
 #include "catalog/pg_collation_d.h"
 #include "catalog/pg_type.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "trgm.h"
 #include "tsearch/ts_locale.h"
 #include "utils/formatting.h"
diff --git a/contrib/pg_visibility/pg_visibility.c b/contrib/pg_visibility/pg_visibility.c
index dfab0b64cf5..fb97bfba541 100644
--- a/contrib/pg_visibility/pg_visibility.c
+++ b/contrib/pg_visibility/pg_visibility.c
@@ -17,7 +17,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage_xlog.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
diff --git a/contrib/pg_walinspect/pg_walinspect.c b/contrib/pg_walinspect/pg_walinspect.c
index 4cf6e41e2f5..13a93f35eb1 100644
--- a/contrib/pg_walinspect/pg_walinspect.c
+++ b/contrib/pg_walinspect/pg_walinspect.c
@@ -20,6 +20,7 @@
 #include "access/xlogstats.h"
 #include "access/xlogutils.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "utils/array.h"
diff --git a/contrib/pgcrypto/crypt-blowfish.c b/contrib/pgcrypto/crypt-blowfish.c
index 563393c9284..40b9ad1fa94 100644
--- a/contrib/pgcrypto/crypt-blowfish.c
+++ b/contrib/pgcrypto/crypt-blowfish.c
@@ -33,7 +33,8 @@
  */
 
 #include "postgres.h"
-#include "miscadmin.h"
+
+#include "ipc/interrupt.h"
 
 #include "px-crypt.h"
 #include "px.h"
diff --git a/contrib/pgcrypto/crypt-des.c b/contrib/pgcrypto/crypt-des.c
index 98c30ea122e..1ee27a94cdf 100644
--- a/contrib/pgcrypto/crypt-des.c
+++ b/contrib/pgcrypto/crypt-des.c
@@ -61,7 +61,8 @@
  */
 
 #include "postgres.h"
-#include "miscadmin.h"
+
+#include "ipc/interrupt.h"
 #include "port/pg_bswap.h"
 
 #include "px-crypt.h"
diff --git a/contrib/pgcrypto/crypt-sha.c b/contrib/pgcrypto/crypt-sha.c
index 8191ba02b23..8e04249ef8a 100644
--- a/contrib/pgcrypto/crypt-sha.c
+++ b/contrib/pgcrypto/crypt-sha.c
@@ -46,8 +46,8 @@
 #include "postgres.h"
 
 #include "common/string.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 
 #include "px-crypt.h"
 #include "px.h"
diff --git a/contrib/pgstattuple/pgstatapprox.c b/contrib/pgstattuple/pgstatapprox.c
index 21e0b50fb4b..8de6624bff1 100644
--- a/contrib/pgstattuple/pgstatapprox.c
+++ b/contrib/pgstattuple/pgstatapprox.c
@@ -19,6 +19,7 @@
 #include "catalog/pg_am_d.h"
 #include "commands/vacuum.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c
index 3a3f2637bd9..fb958b01965 100644
--- a/contrib/pgstattuple/pgstatindex.c
+++ b/contrib/pgstattuple/pgstatindex.c
@@ -35,6 +35,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_am.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "storage/read_stream.h"
diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c
index 6a7f8cb4a7c..e83b1bef878 100644
--- a/contrib/pgstattuple/pgstattuple.c
+++ b/contrib/pgstattuple/pgstattuple.c
@@ -33,6 +33,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_am_d.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index aab21695979..77f0c1cf82c 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -34,6 +34,7 @@
 #include "utils/inval.h"
 #include "utils/syscache.h"
 #include "utils/tuplestore.h"
+#include "utils/wait_event.h"
 
 /*
  * Connection cache hash table entry
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6dbae583ecc..00af1ce85ee 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -27,6 +27,7 @@
 #include "executor/spi.h"
 #include "foreign/fdwapi.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
diff --git a/contrib/tsm_system_rows/tsm_system_rows.c b/contrib/tsm_system_rows/tsm_system_rows.c
index a9a24833730..fa5fb401a67 100644
--- a/contrib/tsm_system_rows/tsm_system_rows.c
+++ b/contrib/tsm_system_rows/tsm_system_rows.c
@@ -30,7 +30,7 @@
 
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "utils/sampling.h"
 
diff --git a/contrib/tsm_system_time/tsm_system_time.c b/contrib/tsm_system_time/tsm_system_time.c
index 4814c31bc6f..b50a5f60923 100644
--- a/contrib/tsm_system_time/tsm_system_time.c
+++ b/contrib/tsm_system_time/tsm_system_time.c
@@ -28,7 +28,7 @@
 
 #include "access/tsmapi.h"
 #include "catalog/pg_type.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "optimizer/optimizer.h"
 #include "portability/instr_time.h"
 #include "utils/sampling.h"
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index bdb30752e09..77439feb0f5 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -29,7 +29,7 @@
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/brin/brin_pageops.c b/src/backend/access/brin/brin_pageops.c
index 7da97bec43b..308ffd7e567 100644
--- a/src/backend/access/brin/brin_pageops.c
+++ b/src/backend/access/brin/brin_pageops.c
@@ -15,7 +15,7 @@
 #include "access/brin_revmap.h"
 #include "access/brin_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/freespace.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/brin/brin_revmap.c b/src/backend/access/brin/brin_revmap.c
index 233355cb2d5..526ba747c60 100644
--- a/src/backend/access/brin/brin_revmap.c
+++ b/src/backend/access/brin/brin_revmap.c
@@ -27,7 +27,7 @@
 #include "access/brin_xlog.h"
 #include "access/rmgr.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c
index 77d42e7ed65..6a0034572c9 100644
--- a/src/backend/access/common/toast_internals.c
+++ b/src/backend/access/common/toast_internals.c
@@ -21,7 +21,7 @@
 #include "access/toast_internals.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/fmgroids.h"
 #include "utils/rel.h"
 #include "utils/snapmgr.h"
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index 3d3a9da56b1..c4f28bfb11f 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -17,7 +17,7 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
diff --git a/src/backend/access/gin/gindatapage.c b/src/backend/access/gin/gindatapage.c
index c5d7db28077..06c996f6fc0 100644
--- a/src/backend/access/gin/gindatapage.c
+++ b/src/backend/access/gin/gindatapage.c
@@ -17,8 +17,8 @@
 #include "access/gin_private.h"
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/gin/ginfast.c b/src/backend/access/gin/ginfast.c
index f50848eb65a..ddf414660e0 100644
--- a/src/backend/access/gin/ginfast.c
+++ b/src/backend/access/gin/ginfast.c
@@ -24,6 +24,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_am.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/gin/ginget.c b/src/backend/access/gin/ginget.c
index 6b148e69a8e..0ef55d304b8 100644
--- a/src/backend/access/gin/ginget.c
+++ b/src/backend/access/gin/ginget.c
@@ -17,6 +17,7 @@
 #include "access/gin_private.h"
 #include "access/relscan.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/predicate.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index cb9ed3b563c..ebe92d9ba1e 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -24,7 +24,7 @@
 #include "catalog/pg_collation.h"
 #include "commands/progress.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index e7cba81d477..45e9abd5ab3 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -22,7 +22,7 @@
 #include "catalog/pg_type.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/indexfsm.h"
 #include "utils/builtins.h"
 #include "utils/index_selfuncs.h"
diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c
index 840543eb664..1a57797ea17 100644
--- a/src/backend/access/gin/ginvacuum.c
+++ b/src/backend/access/gin/ginvacuum.c
@@ -18,6 +18,7 @@
 #include "access/ginxlog.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index 8565e225be7..468b53b3783 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -19,6 +19,7 @@
 #include "access/xloginsert.h"
 #include "catalog/pg_collation.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index 7f57c787f4c..8fb29e8f853 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -38,6 +38,7 @@
 #include "access/gist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "optimizer/optimizer.h"
diff --git a/src/backend/access/gist/gistget.c b/src/backend/access/gist/gistget.c
index 4d7c100d737..49cac5cf550 100644
--- a/src/backend/access/gist/gistget.c
+++ b/src/backend/access/gist/gistget.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/relscan.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/float.h"
diff --git a/src/backend/access/gist/gistvacuum.c b/src/backend/access/gist/gistvacuum.c
index 686a0418054..730ca03ee6e 100644
--- a/src/backend/access/gist/gistvacuum.c
+++ b/src/backend/access/gist/gistvacuum.c
@@ -18,8 +18,8 @@
 #include "access/gist_private.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
+#include "ipc/interrupt.h"
 #include "lib/integerset.h"
-#include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
 #include "storage/read_stream.h"
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 8d8cd30dc38..7eec90e8c21 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -26,7 +26,7 @@
 #include "access/xloginsert.h"
 #include "commands/progress.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "optimizer/plancat.h"
 #include "pgstat.h"
diff --git a/src/backend/access/hash/hashinsert.c b/src/backend/access/hash/hashinsert.c
index 3395bbc13f8..707b61c14ae 100644
--- a/src/backend/access/hash/hashinsert.c
+++ b/src/backend/access/hash/hashinsert.c
@@ -18,7 +18,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/hash/hashovfl.c b/src/backend/access/hash/hashovfl.c
index dbc57ef958c..ddfc3404750 100644
--- a/src/backend/access/hash/hashovfl.c
+++ b/src/backend/access/hash/hashovfl.c
@@ -20,7 +20,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/access/hash/hashpage.c b/src/backend/access/hash/hashpage.c
index 8099b0d021f..761de7f126f 100644
--- a/src/backend/access/hash/hashpage.c
+++ b/src/backend/access/hash/hashpage.c
@@ -31,7 +31,7 @@
 #include "access/hash.h"
 #include "access/hash_xlog.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/predicate.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/hash/hashsearch.c b/src/backend/access/hash/hashsearch.c
index 89d1c5bc6d7..4c6a8980e52 100644
--- a/src/backend/access/hash/hashsearch.c
+++ b/src/backend/access/hash/hashsearch.c
@@ -16,8 +16,8 @@
 
 #include "access/hash.h"
 #include "access/relscan.h"
-#include "miscadmin.h"
 #include "executor/instrument_node.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/hash/hashsort.c b/src/backend/access/hash/hashsort.c
index 77bbfaa461b..1bbf0d33966 100644
--- a/src/backend/access/hash/hashsort.c
+++ b/src/backend/access/hash/hashsort.c
@@ -27,7 +27,7 @@
 
 #include "access/hash.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2268cc277bc..3fad977fc9d 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -35,7 +35,7 @@
 #include "catalog/storage_xlog.h"
 #include "commands/progress.h"
 #include "executor/executor.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/bufpage.h"
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index fdddd23035b..53ad41010a3 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -24,7 +24,7 @@
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c
index 39395aed0d5..75d8554e16c 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -143,7 +143,7 @@
 #include "common/int.h"
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "portability/instr_time.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index 4fd470702aa..256b8507448 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -101,7 +101,7 @@
 #include "access/visibilitymap.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "port/pg_bitutils.h"
 #include "storage/bufmgr.h"
 #include "storage/smgr.h"
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index 1408989c568..f1aa144f96b 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -25,6 +25,7 @@
 #include "access/tableam.h"
 #include "access/transam.h"
 #include "catalog/index.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "miscadmin.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/access/nbtree/nbtdedup.c b/src/backend/access/nbtree/nbtdedup.c
index af7affdf409..d580c2fc068 100644
--- a/src/backend/access/nbtree/nbtdedup.c
+++ b/src/backend/access/nbtree/nbtdedup.c
@@ -18,7 +18,7 @@
 #include "access/nbtxlog.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 static void _bt_bottomupdel_finish_pending(Page page, BTDedupState state,
diff --git a/src/backend/access/nbtree/nbtinsert.c b/src/backend/access/nbtree/nbtinsert.c
index c8af97dd23d..95c298db8fd 100644
--- a/src/backend/access/nbtree/nbtinsert.c
+++ b/src/backend/access/nbtree/nbtinsert.c
@@ -22,8 +22,8 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "storage/predicate.h"
 #include "utils/injection_point.h"
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 0547038616e..ddd835d9b14 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -29,6 +29,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/indexfsm.h"
 #include "storage/predicate.h"
diff --git a/src/backend/access/nbtree/nbtsearch.c b/src/backend/access/nbtree/nbtsearch.c
index aae6acb7f57..44a9ab3da4b 100644
--- a/src/backend/access/nbtree/nbtsearch.c
+++ b/src/backend/access/nbtree/nbtsearch.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/xact.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/predicate.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index 756dfa3dcf4..3fba93e8b7a 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -49,7 +49,7 @@
 #include "catalog/index.h"
 #include "commands/progress.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bulk_write.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/access/spgist/spgdoinsert.c b/src/backend/access/spgist/spgdoinsert.c
index 7c7371c69e8..a8dff69cdca 100644
--- a/src/backend/access/spgist/spgdoinsert.c
+++ b/src/backend/access/spgist/spgdoinsert.c
@@ -21,7 +21,7 @@
 #include "access/xloginsert.h"
 #include "common/int.h"
 #include "common/pg_prng.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "utils/rel.h"
 
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index 780ef646a54..c2d7caf6f68 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -20,7 +20,7 @@
 #include "access/spgist_private.h"
 #include "access/tableam.h"
 #include "access/xloginsert.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "storage/bufmgr.h"
 #include "storage/bulk_write.h"
diff --git a/src/backend/access/spgist/spgscan.c b/src/backend/access/spgist/spgscan.c
index 2cc5f06f5d7..475a50cddcf 100644
--- a/src/backend/access/spgist/spgscan.c
+++ b/src/backend/access/spgist/spgscan.c
@@ -19,7 +19,7 @@
 #include "access/relscan.h"
 #include "access/spgist_private.h"
 #include "executor/instrument_node.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "utils/datum.h"
diff --git a/src/backend/access/spgist/spgvacuum.c b/src/backend/access/spgist/spgvacuum.c
index c461f8dc02d..80197e121b2 100644
--- a/src/backend/access/spgist/spgvacuum.c
+++ b/src/backend/access/spgist/spgvacuum.c
@@ -21,7 +21,7 @@
 #include "access/transam.h"
 #include "access/xloginsert.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/indexfsm.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/access/transam/generic_xlog.c b/src/backend/access/transam/generic_xlog.c
index 7f82186d0d6..6eca2451a70 100644
--- a/src/backend/access/transam/generic_xlog.c
+++ b/src/backend/access/transam/generic_xlog.c
@@ -16,7 +16,7 @@
 #include "access/bufmask.h"
 #include "access/generic_xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 /*-------------------------------------------------------------------------
  * Internally, a delta between pages consists of a set of fragments.  Each
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 10cbc0d76bd..1000e533707 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -76,7 +76,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/access/transam/slru.c b/src/backend/access/transam/slru.c
index 47dd52d6749..38eaab118c1 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -66,7 +66,7 @@
 #include "access/transam.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/fd.h"
 #include "storage/shmem.h"
diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c
index 1035e8b3fc7..3e5aa3690e4 100644
--- a/src/backend/access/transam/twophase.c
+++ b/src/backend/access/transam/twophase.c
@@ -91,7 +91,7 @@
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "replication/origin.h"
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 65bbaeda59c..930d632f773 100644
--- a/src/backend/access/transam/xlogfuncs.c
+++ b/src/backend/access/transam/xlogfuncs.c
@@ -25,7 +25,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "utils/acl.h"
 #include "replication/walreceiver.h"
diff --git a/src/backend/access/transam/xlogutils.c b/src/backend/access/transam/xlogutils.c
index fdc341d8fa4..e52138835f0 100644
--- a/src/backend/access/transam/xlogutils.c
+++ b/src/backend/access/transam/xlogutils.c
@@ -23,7 +23,7 @@
 #include "access/xlogrecovery.h"
 #include "access/xlog_internal.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/fd.h"
 #include "storage/smgr.h"
 #include "utils/hsearch.h"
diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c
index 582dde3b061..d24855385d1 100644
--- a/src/backend/access/transam/xlogwait.c
+++ b/src/backend/access/transam/xlogwait.c
@@ -51,7 +51,7 @@
 #include "access/xlog.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogwait.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/walreceiver.h"
 #include "storage/latch.h"
diff --git a/src/backend/backup/basebackup.c b/src/backend/backup/basebackup.c
index 9c79dadaacc..628ad9a0ed8 100644
--- a/src/backend/backup/basebackup.c
+++ b/src/backend/backup/basebackup.c
@@ -28,8 +28,8 @@
 #include "common/compression.h"
 #include "common/file_perm.h"
 #include "common/file_utils.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "pgstat.h"
 #include "pgtar.h"
diff --git a/src/backend/backup/basebackup_throttle.c b/src/backend/backup/basebackup_throttle.c
index 4d8d90f356b..25527784217 100644
--- a/src/backend/backup/basebackup_throttle.c
+++ b/src/backend/backup/basebackup_throttle.c
@@ -15,7 +15,7 @@
 #include "postgres.h"
 
 #include "backup/basebackup_sink.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/latch.h"
 #include "utils/timestamp.h"
diff --git a/src/backend/backup/walsummaryfuncs.c b/src/backend/backup/walsummaryfuncs.c
index f83c1604263..dc27a0926d6 100644
--- a/src/backend/backup/walsummaryfuncs.c
+++ b/src/backend/backup/walsummaryfuncs.c
@@ -16,7 +16,7 @@
 #include "backup/walsummary.h"
 #include "common/blkreftable.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "utils/fmgrprotos.h"
 #include "utils/pg_lsn.h"
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 943ff4733d3..c4ad11be0b8 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -28,7 +28,7 @@
 #include "catalog/pg_tablespace.h"
 #include "catalog/toasting.h"
 #include "commands/defrem.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 7be49032934..4f2e8645dab 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -41,6 +41,7 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_tablespace.h"
 #include "catalog/pg_type.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "utils/fmgroids.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 9407c357f27..b93fb097cca 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -58,7 +58,7 @@
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index e443a4993c5..21091392189 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -26,7 +26,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bulk_write.h"
 #include "storage/freespace.h"
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f66e80b757c..1cb087a0b83 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -36,7 +36,7 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "foreign/fdwapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 0087585b2c4..e6ba012e7d6 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -38,8 +38,8 @@
 #include "executor/nodeModifyTable.h"
 #include "executor/tuptable.h"
 #include "foreign/fdwapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index f0819d15ab7..38fcfca8ba5 100644
--- a/src/backend/commands/dbcommands.c
+++ b/src/backend/commands/dbcommands.c
@@ -49,8 +49,8 @@
 #include "commands/seclabel.h"
 #include "commands/tablespace.h"
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgwriter.h"
 #include "replication/slot.h"
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..9e4b426e0b7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -44,8 +44,8 @@
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c
index f7d8007f796..2e5fc73beae 100644
--- a/src/backend/commands/matview.c
+++ b/src/backend/commands/matview.c
@@ -30,7 +30,7 @@
 #include "commands/tablespace.h"
 #include "executor/executor.h"
 #include "executor/spi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "rewrite/rewriteHandler.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/commands/publicationcmds.c b/src/backend/commands/publicationcmds.c
index 440adb356ad..ed2d2296f8c 100644
--- a/src/backend/commands/publicationcmds.c
+++ b/src/backend/commands/publicationcmds.c
@@ -32,6 +32,7 @@
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
 #include "commands/publicationcmds.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_clause.h"
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index a2a4c286772..4c5e8ea7a1c 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -58,9 +58,9 @@
 #include "commands/tablecmds.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
-#include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
 #include "replication/logicalrelation.h"
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 9ff2e14904c..5888a5a1fe1 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -21,6 +21,7 @@
 #include "access/xlogwait.h"
 #include "commands/repack.h"
 #include "commands/repack_internal.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqmq.h"
 #include "replication/snapbuild.h"
 #include "storage/ipc.h"
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 551667650ba..4fcc369e490 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -34,7 +34,7 @@
 #include "commands/sequence_xlog.h"
 #include "commands/tablecmds.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_type.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..21e74184b61 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -42,7 +42,7 @@
 #include "commands/progress.h"
 #include "commands/repack.h"
 #include "commands/vacuum.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index d96d4f9947b..c52dc70b544 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -22,6 +22,7 @@
 #include "executor/executor.h"
 #include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "partitioning/partbounds.h"
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 7c4c66e323f..81a975680ee 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -117,6 +117,7 @@
 #include "executor/nodeValuesscan.h"
 #include "executor/nodeWindowAgg.h"
 #include "executor/nodeWorktablescan.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 
diff --git a/src/backend/executor/execSRF.c b/src/backend/executor/execSRF.c
index 8aedcc6a459..d95f066fa4e 100644
--- a/src/backend/executor/execSRF.c
+++ b/src/backend/executor/execSRF.c
@@ -22,7 +22,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_proc.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/nodeFuncs.h"
 #include "parser/parse_coerce.h"
 #include "pgstat.h"
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c
index 925caadd2ce..ddd0019b452 100644
--- a/src/backend/executor/nodeAgg.c
+++ b/src/backend/executor/nodeAgg.c
@@ -259,6 +259,7 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeAgg.h"
+#include "ipc/interrupt.h"
 #include "lib/hyperloglog.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 987358e27fa..2c67218912e 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -62,7 +62,7 @@
 #include "executor/execPartition.h"
 #include "executor/executor.h"
 #include "executor/nodeAppend.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/latch.h"
 #include "storage/lwlock.h"
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 83d6478bc2b..e0fcf46d3b3 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -41,7 +41,7 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeBitmapHeapscan.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/bufmgr.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/executor/nodeBitmapIndexscan.c b/src/backend/executor/nodeBitmapIndexscan.c
index 7978514e1bc..28da3083fa6 100644
--- a/src/backend/executor/nodeBitmapIndexscan.c
+++ b/src/backend/executor/nodeBitmapIndexscan.c
@@ -26,6 +26,7 @@
 #include "executor/instrument.h"
 #include "executor/nodeBitmapIndexscan.h"
 #include "executor/nodeIndexscan.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/tidbitmap.h"
 
diff --git a/src/backend/executor/nodeCustom.c b/src/backend/executor/nodeCustom.c
index b7cc890cd20..b520fb13070 100644
--- a/src/backend/executor/nodeCustom.c
+++ b/src/backend/executor/nodeCustom.c
@@ -13,7 +13,7 @@
 #include "access/parallel.h"
 #include "executor/executor.h"
 #include "executor/nodeCustom.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "nodes/extensible.h"
 #include "nodes/plannodes.h"
diff --git a/src/backend/executor/nodeGather.c b/src/backend/executor/nodeGather.c
index 114693abb32..e500ae668d0 100644
--- a/src/backend/executor/nodeGather.c
+++ b/src/backend/executor/nodeGather.c
@@ -34,6 +34,7 @@
 #include "executor/executor.h"
 #include "executor/nodeGather.h"
 #include "executor/tqueue.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "storage/latch.h"
diff --git a/src/backend/executor/nodeGatherMerge.c b/src/backend/executor/nodeGatherMerge.c
index c2ac5e0792c..d9e4b559315 100644
--- a/src/backend/executor/nodeGatherMerge.c
+++ b/src/backend/executor/nodeGatherMerge.c
@@ -19,8 +19,8 @@
 #include "executor/execParallel.h"
 #include "executor/nodeGatherMerge.h"
 #include "executor/tqueue.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
-#include "miscadmin.h"
 #include "optimizer/optimizer.h"
 #include "utils/sortsupport.h"
 
diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c
index 3699d8a3746..6ad97418b1d 100644
--- a/src/backend/executor/nodeGroup.c
+++ b/src/backend/executor/nodeGroup.c
@@ -25,7 +25,7 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeGroup.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 
 /*
diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 8825bb6fa23..bf137e0ec6b 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -35,6 +35,7 @@
 #include "executor/instrument.h"
 #include "executor/nodeHash.h"
 #include "executor/nodeHashjoin.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "utils/lsyscache.h"
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 0b365d5b475..87bb92b5327 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -169,7 +169,7 @@
 #include "executor/instrument.h"
 #include "executor/nodeHash.h"
 #include "executor/nodeHashjoin.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/lsyscache.h"
 #include "utils/sharedtuplestore.h"
 #include "utils/tuplestore.h"
diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c
index 1d831049b65..3dab301a2a8 100644
--- a/src/backend/executor/nodeIncrementalSort.c
+++ b/src/backend/executor/nodeIncrementalSort.c
@@ -80,6 +80,7 @@
 
 #include "executor/execdebug.h"
 #include "executor/nodeIncrementalSort.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index d52012e8a69..821a178b08f 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -40,7 +40,7 @@
 #include "executor/instrument.h"
 #include "executor/nodeIndexonlyscan.h"
 #include "executor/nodeIndexscan.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/predicate.h"
 #include "utils/builtins.h"
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 39f6691ee35..0d74b6c77ad 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -36,8 +36,8 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeIndexscan.h"
+#include "ipc/interrupt.h"
 #include "lib/pairingheap.h"
-#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "utils/array.h"
 #include "utils/datum.h"
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index 8f75cbbead2..d99ded90e30 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -23,7 +23,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeLimit.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 static void recompute_limits(LimitState *node);
 static int64 compute_tuples_needed(LimitState *node);
diff --git a/src/backend/executor/nodeLockRows.c b/src/backend/executor/nodeLockRows.c
index 3bee818a8b4..e1daaed25ba 100644
--- a/src/backend/executor/nodeLockRows.c
+++ b/src/backend/executor/nodeLockRows.c
@@ -26,7 +26,7 @@
 #include "executor/executor.h"
 #include "executor/nodeLockRows.h"
 #include "foreign/fdwapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/rel.h"
 
 
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index e5f387612bc..c8d975f8e06 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -23,6 +23,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeMaterial.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "utils/tuplestore.h"
 
diff --git a/src/backend/executor/nodeMemoize.c b/src/backend/executor/nodeMemoize.c
index fdca97d7426..11856ce7148 100644
--- a/src/backend/executor/nodeMemoize.c
+++ b/src/backend/executor/nodeMemoize.c
@@ -70,6 +70,7 @@
 #include "common/hashfn.h"
 #include "executor/executor.h"
 #include "executor/nodeMemoize.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "utils/datum.h"
diff --git a/src/backend/executor/nodeMergeAppend.c b/src/backend/executor/nodeMergeAppend.c
index 0173074d85d..400c2012b33 100644
--- a/src/backend/executor/nodeMergeAppend.c
+++ b/src/backend/executor/nodeMergeAppend.c
@@ -42,8 +42,8 @@
 #include "executor/executor.h"
 #include "executor/execPartition.h"
 #include "executor/nodeMergeAppend.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
-#include "miscadmin.h"
 #include "utils/sortsupport.h"
 
 /*
diff --git a/src/backend/executor/nodeMergejoin.c b/src/backend/executor/nodeMergejoin.c
index f8421a74c75..be0f0ecc21b 100644
--- a/src/backend/executor/nodeMergejoin.c
+++ b/src/backend/executor/nodeMergejoin.c
@@ -96,7 +96,7 @@
 #include "executor/execdebug.h"
 #include "executor/instrument.h"
 #include "executor/nodeMergejoin.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/lsyscache.h"
 #include "utils/sortsupport.h"
 
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index 846dc516b43..a0227679dd8 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -63,7 +63,7 @@
 #include "executor/instrument.h"
 #include "executor/nodeModifyTable.h"
 #include "foreign/fdwapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "pgstat.h"
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 809311ab513..beac167acc8 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -24,7 +24,7 @@
 #include "executor/execdebug.h"
 #include "executor/instrument.h"
 #include "executor/nodeNestloop.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeProjectSet.c b/src/backend/executor/nodeProjectSet.c
index 33b5c209f5a..f6890d90eb3 100644
--- a/src/backend/executor/nodeProjectSet.c
+++ b/src/backend/executor/nodeProjectSet.c
@@ -24,7 +24,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeProjectSet.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/nodeFuncs.h"
 
 
diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c
index 7166397e59b..933db2c509d 100644
--- a/src/backend/executor/nodeRecursiveunion.c
+++ b/src/backend/executor/nodeRecursiveunion.c
@@ -20,6 +20,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeRecursiveunion.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "utils/memutils.h"
 #include "utils/tuplestore.h"
diff --git a/src/backend/executor/nodeResult.c b/src/backend/executor/nodeResult.c
index 660561aed8d..f01f0473c8e 100644
--- a/src/backend/executor/nodeResult.c
+++ b/src/backend/executor/nodeResult.c
@@ -47,7 +47,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeResult.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c
index 24709778384..22c862945ec 100644
--- a/src/backend/executor/nodeSetOp.c
+++ b/src/backend/executor/nodeSetOp.c
@@ -48,7 +48,7 @@
 #include "access/htup_details.h"
 #include "executor/executor.h"
 #include "executor/nodeSetOp.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "utils/memutils.h"
 #include "utils/sortsupport.h"
 
diff --git a/src/backend/executor/nodeSort.c b/src/backend/executor/nodeSort.c
index e02313f7813..d91f64d2b9a 100644
--- a/src/backend/executor/nodeSort.c
+++ b/src/backend/executor/nodeSort.c
@@ -18,6 +18,7 @@
 #include "access/parallel.h"
 #include "executor/execdebug.h"
 #include "executor/nodeSort.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "utils/tuplesort.h"
 
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index 8285c7101c2..a7178ee7d69 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -29,7 +29,7 @@
 #include "access/htup_details.h"
 #include "executor/executor.h"
 #include "executor/nodeSubplan.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "utils/array.h"
diff --git a/src/backend/executor/nodeTableFuncscan.c b/src/backend/executor/nodeTableFuncscan.c
index 9394156f405..71d54eb5c04 100644
--- a/src/backend/executor/nodeTableFuncscan.c
+++ b/src/backend/executor/nodeTableFuncscan.c
@@ -25,6 +25,7 @@
 #include "executor/executor.h"
 #include "executor/nodeTableFuncscan.h"
 #include "executor/tablefunc.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "utils/builtins.h"
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 6641df10999..ba162ff7306 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -27,8 +27,8 @@
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
 #include "executor/nodeTidscan.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
-#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "utils/array.h"
 #include "utils/rel.h"
diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c
index 0898d4e693b..c03812862c5 100644
--- a/src/backend/executor/nodeUnique.c
+++ b/src/backend/executor/nodeUnique.c
@@ -35,7 +35,7 @@
 
 #include "executor/executor.h"
 #include "executor/nodeUnique.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 
 /* ----------------------------------------------------------------
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index f1c524d00df..d100b3af217 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -41,6 +41,7 @@
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeWindowAgg.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
diff --git a/src/backend/lib/bipartite_match.c b/src/backend/lib/bipartite_match.c
index 12520dc91b4..5bfd4d4fc46 100644
--- a/src/backend/lib/bipartite_match.c
+++ b/src/backend/lib/bipartite_match.c
@@ -18,6 +18,7 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "lib/bipartite_match.h"
 #include "miscadmin.h"
 
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 2af5615e54a..d7bd3269d69 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -26,6 +26,7 @@
 #include "commands/user.h"
 #include "common/ip.h"
 #include "common/md5.h"
+#include "ipc/interrupt.h"
 #include "libpq/auth.h"
 #include "libpq/crypt.h"
 #include "libpq/libpq.h"
@@ -33,7 +34,6 @@
 #include "libpq/pqformat.h"
 #include "libpq/sasl.h"
 #include "libpq/scram.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "replication/walsender.h"
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 4a442f22df6..34ec18813b4 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -73,8 +73,8 @@
 #endif
 
 #include "common/ip.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
-#include "miscadmin.h"
 #include "port/pg_bswap.h"
 #include "postmaster/postmaster.h"
 #include "storage/ipc.h"
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index 238fb73ebd8..c11433968c8 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -15,10 +15,10 @@
 
 #include "access/parallel.h"
 #include "commands/repack.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logicalworker.h"
 #include "storage/latch.h"
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index 4424fdbe906..8e582d0fec0 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -29,6 +29,7 @@
 #include "access/table.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/multibitmapset.h"
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 73518c8f870..f5ea9102e2d 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "executor/nodeSetOp.h"
 #include "foreign/fdwapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/extensible.h"
 #include "optimizer/appendinfo.h"
diff --git a/src/backend/optimizer/util/predtest.c b/src/backend/optimizer/util/predtest.c
index 690a23d619a..489a233e81a 100644
--- a/src/backend/optimizer/util/predtest.c
+++ b/src/backend/optimizer/util/predtest.c
@@ -19,7 +19,7 @@
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
diff --git a/src/backend/partitioning/partbounds.c b/src/backend/partitioning/partbounds.c
index 6fb150a8763..db7fac10075 100644
--- a/src/backend/partitioning/partbounds.c
+++ b/src/backend/partitioning/partbounds.c
@@ -24,7 +24,7 @@
 #include "commands/tablecmds.h"
 #include "common/hashfn.h"
 #include "executor/executor.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/pathnodes.h"
diff --git a/src/backend/port/win32_sema.c b/src/backend/port/win32_sema.c
index a3202554769..59fd4469faf 100644
--- a/src/backend/port/win32_sema.c
+++ b/src/backend/port/win32_sema.c
@@ -13,6 +13,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/ipc.h"
 #include "storage/pg_sema.h"
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index ad4bf4bd2a8..ab2af862d69 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -16,7 +16,7 @@
 #include <signal.h>
 
 #include "access/xlog.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "storage/condition_variable.h"
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index d6121f2b4fe..6b7312b75d9 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -32,8 +32,8 @@
 #include "postgres.h"
 
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 4b051eb3128..bd73888747b 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -44,8 +44,8 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
diff --git a/src/backend/postmaster/datachecksum_state.c b/src/backend/postmaster/datachecksum_state.c
index 47b87f51ad9..03717a8b008 100644
--- a/src/backend/postmaster/datachecksum_state.c
+++ b/src/backend/postmaster/datachecksum_state.c
@@ -198,7 +198,7 @@
 #include "commands/progress.h"
 #include "commands/vacuum.h"
 #include "common/relpath.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/bgwriter.h"
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/postmaster/interrupt.c
index a2c0ff012c5..20f7e34e204 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/postmaster/interrupt.c
@@ -16,6 +16,7 @@
 
 #include <unistd.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "postmaster/interrupt.h"
 #include "storage/ipc.h"
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index a3fd4f83201..8098e29ff65 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -33,6 +33,7 @@
 #include "access/xlog_internal.h"
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index 63624e8caa1..e7db3b3e6e1 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -23,6 +23,7 @@
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
 #include "libpq/pqsignal.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/startup.h"
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index 89301815dc3..ea6afab8d69 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -32,8 +32,8 @@
 #include "catalog/storage_xlog.h"
 #include "commands/dbcommands_xlog.h"
 #include "common/blkreftable.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 3493be8e630..d32297fc259 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -46,7 +46,7 @@
 
 #include "access/xlog.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
index 5376519fea5..16724036ffe 100644
--- a/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
+++ b/src/backend/replication/libpqwalreceiver/libpqwalreceiver.c
@@ -27,15 +27,14 @@
 #include "libpq-fe.h"
 #include "libpq/libpq-be-fe-helpers.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "pqexpbuffer.h"
 #include "replication/walreceiver.h"
-#include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 #include "utils/pg_lsn.h"
 #include "utils/tuplestore.h"
+#include "utils/wait_event.h"
 
 PG_MODULE_MAGIC_EXT(
 					.name = "libpqwalreceiver",
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 9535abef122..6e5f3ab123f 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -157,6 +157,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 313e31ff2e3..920c5a3de99 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -25,8 +25,8 @@
 #include "catalog/pg_subscription.h"
 #include "catalog/pg_subscription_rel.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 3541fc793e4..ef7e7e9a8c8 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -32,7 +32,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogutils.h"
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
diff --git a/src/backend/replication/logical/logicalctl.c b/src/backend/replication/logical/logicalctl.c
index c11d1316450..b92e48ee3d6 100644
--- a/src/backend/replication/logical/logicalctl.c
+++ b/src/backend/replication/logical/logicalctl.c
@@ -65,7 +65,7 @@
 
 #include "access/xloginsert.h"
 #include "catalog/pg_control.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "replication/slot.h"
 #include "storage/ipc.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/replication/logical/logicalfuncs.c b/src/backend/replication/logical/logicalfuncs.c
index 71fbaf72269..a4cbf6e6cbf 100644
--- a/src/backend/replication/logical/logicalfuncs.c
+++ b/src/backend/replication/logical/logicalfuncs.c
@@ -22,8 +22,8 @@
 #include "catalog/pg_type.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
diff --git a/src/backend/replication/logical/origin.c b/src/backend/replication/logical/origin.c
index c9dfb094c2b..365f8757051 100644
--- a/src/backend/replication/logical/origin.c
+++ b/src/backend/replication/logical/origin.c
@@ -79,7 +79,7 @@
 #include "catalog/indexing.h"
 #include "catalog/pg_subscription.h"
 #include "funcapi.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 #include "pgstat.h"
 #include "replication/origin.h"
diff --git a/src/backend/replication/logical/reorderbuffer.c b/src/backend/replication/logical/reorderbuffer.c
index 059ed860314..8e5adbd2c4e 100644
--- a/src/backend/replication/logical/reorderbuffer.c
+++ b/src/backend/replication/logical/reorderbuffer.c
@@ -97,8 +97,8 @@
 #include "access/xlog_internal.h"
 #include "catalog/catalog.h"
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "lib/binaryheap.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "replication/logical.h"
 #include "replication/reorderbuffer.h"
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index f47f962c7db..1cfd9a36b34 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -56,6 +56,7 @@
 #include "catalog/pg_sequence.h"
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 7d2999d77fd..489c115f45f 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -63,6 +63,7 @@
 #include "access/xlog_internal.h"
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/replication/logical/tablesync.c b/src/backend/replication/logical/tablesync.c
index a04b84ebc1d..d6311266fe5 100644
--- a/src/backend/replication/logical/tablesync.c
+++ b/src/backend/replication/logical/tablesync.c
@@ -100,7 +100,7 @@
 #include "catalog/pg_subscription_rel.h"
 #include "catalog/pg_type.h"
 #include "commands/copy.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/makefuncs.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d7fb9f5a67f..010657fb400 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -44,7 +44,7 @@
 #include "access/xlogrecovery.h"
 #include "common/file_utils.h"
 #include "common/string.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
diff --git a/src/backend/replication/syncrep.c b/src/backend/replication/syncrep.c
index e0e30579c59..3f1da4dbe4e 100644
--- a/src/backend/replication/syncrep.c
+++ b/src/backend/replication/syncrep.c
@@ -76,7 +76,7 @@
 
 #include "access/xact.h"
 #include "common/int.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/syncrep.h"
 #include "replication/walsender.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index 4319cb6fd43..d7804a73454 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -60,9 +60,9 @@
 #include "access/xlogwait.h"
 #include "catalog/pg_authid.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/storage/aio/aio.c b/src/backend/storage/aio/aio.c
index 8f7e26607b9..1d4073ed0da 100644
--- a/src/backend/storage/aio/aio.c
+++ b/src/backend/storage/aio/aio.c
@@ -38,6 +38,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "port/atomics.h"
diff --git a/src/backend/storage/aio/aio_callback.c b/src/backend/storage/aio/aio_callback.c
index 206b81f5028..fcd6c916af0 100644
--- a/src/backend/storage/aio/aio_callback.c
+++ b/src/backend/storage/aio/aio_callback.c
@@ -15,7 +15,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/storage/aio/aio_io.c b/src/backend/storage/aio/aio_io.c
index 72b4c9feb3a..da7ed6ca53c 100644
--- a/src/backend/storage/aio/aio_io.c
+++ b/src/backend/storage/aio/aio_io.c
@@ -18,7 +18,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/fd.h"
diff --git a/src/backend/storage/aio/method_io_uring.c b/src/backend/storage/aio/method_io_uring.c
index c0f9fc9c303..e2a2d85589b 100644
--- a/src/backend/storage/aio/method_io_uring.c
+++ b/src/backend/storage/aio/method_io_uring.c
@@ -34,7 +34,7 @@
 
 #include <liburing.h>
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/aio_internal.h"
 #include "storage/fd.h"
 #include "storage/proc.h"
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index 63e34d66690..abffeefc2c1 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -30,8 +30,8 @@
 
 #include <limits.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/pqsignal.h"
-#include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/interrupt.h"
diff --git a/src/backend/storage/file/buffile.c b/src/backend/storage/file/buffile.c
index c4afe4d368a..44798943ead 100644
--- a/src/backend/storage/file/buffile.c
+++ b/src/backend/storage/file/buffile.c
@@ -47,7 +47,7 @@
 
 #include "commands/tablespace.h"
 #include "executor/instrument.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/buffile.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/storage/file/copydir.c b/src/backend/storage/file/copydir.c
index 5ee141f13a5..36118d74f1b 100644
--- a/src/backend/storage/file/copydir.c
+++ b/src/backend/storage/file/copydir.c
@@ -25,7 +25,7 @@
 #include <unistd.h>
 
 #include "common/file_utils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/copydir.h"
 #include "storage/fd.h"
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 4cf4717f764..7b4cb9a126c 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -91,7 +91,7 @@
 #include "common/file_perm.h"
 #include "common/file_utils.h"
 #include "common/pg_prng.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/startup.h"
 #include "storage/aio.h"
diff --git a/src/backend/storage/freespace/freespace.c b/src/backend/storage/freespace/freespace.c
index 006edab9d77..23ee27578dc 100644
--- a/src/backend/storage/freespace/freespace.c
+++ b/src/backend/storage/freespace/freespace.c
@@ -26,7 +26,7 @@
 #include "access/htup_details.h"
 #include "access/xloginsert.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/freespace.h"
 #include "storage/fsm_internals.h"
 #include "storage/smgr.h"
diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c
index 8b69df4ff26..6cf45cfac8c 100644
--- a/src/backend/storage/ipc/dsm.c
+++ b/src/backend/storage/ipc/dsm.c
@@ -34,6 +34,7 @@
 #include <sys/stat.h>
 
 #include "common/pg_prng.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
diff --git a/src/backend/storage/ipc/ipc.c b/src/backend/storage/ipc/ipc.c
index cb944edd8df..651e9c23e16 100644
--- a/src/backend/storage/ipc/ipc.c
+++ b/src/backend/storage/ipc/ipc.c
@@ -23,6 +23,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #ifdef PROFILE_PID_DIR
 #include "postmaster/autovacuum.h"
diff --git a/src/backend/storage/ipc/procsignal.c b/src/backend/storage/ipc/procsignal.c
index 00b640f2d53..737ad81363c 100644
--- a/src/backend/storage/ipc/procsignal.c
+++ b/src/backend/storage/ipc/procsignal.c
@@ -20,7 +20,7 @@
 #include "access/parallel.h"
 #include "commands/async.h"
 #include "commands/repack.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/datachecksum_state.h"
diff --git a/src/backend/storage/ipc/shm_mq.c b/src/backend/storage/ipc/shm_mq.c
index 26b24158ed9..ef0195f9cfa 100644
--- a/src/backend/storage/ipc/shm_mq.c
+++ b/src/backend/storage/ipc/shm_mq.c
@@ -18,7 +18,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/bgworker.h"
diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index f1f7cd3a4ff..cb4e4111cfa 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -133,6 +133,7 @@
 #include "access/slru.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "port/pg_bitutils.h"
 #include "port/pg_numa.h"
diff --git a/src/backend/storage/ipc/signalfuncs.c b/src/backend/storage/ipc/signalfuncs.c
index 800b699de21..84e44b398b2 100644
--- a/src/backend/storage/ipc/signalfuncs.c
+++ b/src/backend/storage/ipc/signalfuncs.c
@@ -17,7 +17,7 @@
 #include <signal.h>
 
 #include "catalog/pg_authid.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/syslogger.h"
 #include "storage/pmsignal.h"
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 7f011e04990..e58ac4c4363 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -22,7 +22,7 @@
 #include "access/xloginsert.h"
 #include "access/xlogrecovery.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "replication/slot.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/storage/lmgr/condition_variable.c b/src/backend/storage/lmgr/condition_variable.c
index 1f16b3f7475..3a62a447aca 100644
--- a/src/backend/storage/lmgr/condition_variable.c
+++ b/src/backend/storage/lmgr/condition_variable.c
@@ -20,7 +20,7 @@
 
 #include <limits.h>
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "portability/instr_time.h"
 #include "storage/condition_variable.h"
 #include "storage/proc.h"
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index 2ccf7237fee..3b5c820cc81 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -19,7 +19,7 @@
 #include "access/xact.h"
 #include "catalog/catalog.h"
 #include "commands/progress.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 8d246ed5a4e..5a017f28f60 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -37,7 +37,7 @@
 #include "access/twophase_rmgr.h"
 #include "access/xlog.h"
 #include "access/xlogutils.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "storage/lmgr.h"
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index b1ad396ba79..03c16f7d26d 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -76,7 +76,7 @@
  */
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 28c0efc3e20..d9b0d8f62ce 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -38,7 +38,7 @@
 #include "access/twophase.h"
 #include "access/xlogutils.h"
 #include "access/xlogwait.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "replication/slotsync.h"
diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c
index 1fdfda59edd..6afb3f84513 100644
--- a/src/backend/storage/page/bufpage.c
+++ b/src/backend/storage/page/bufpage.c
@@ -17,6 +17,7 @@
 #include "access/htup_details.h"
 #include "access/itup.h"
 #include "access/xlog.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/checksum.h"
 #include "utils/memdebug.h"
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 5391640d861..a9b6c1ede6c 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -64,8 +64,8 @@
 #include "postgres.h"
 
 #include "access/xlogutils.h"
+#include "ipc/interrupt.h"
 #include "lib/ilist.h"
-#include "miscadmin.h"
 #include "storage/aio.h"
 #include "storage/bufmgr.h"
 #include "storage/ipc.h"
diff --git a/src/backend/tcop/fastpath.c b/src/backend/tcop/fastpath.c
index 52772bc90a8..fd676ea27a2 100644
--- a/src/backend/tcop/fastpath.c
+++ b/src/backend/tcop/fastpath.c
@@ -22,6 +22,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_proc.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "mb/pg_wchar.h"
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index ee731000820..335e2d8998c 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -21,7 +21,7 @@
 #include "commands/prepare.h"
 #include "executor/executor.h"
 #include "executor/tstoreReceiver.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "tcop/pquery.h"
 #include "tcop/utility.h"
diff --git a/src/backend/tsearch/wparser_def.c b/src/backend/tsearch/wparser_def.c
index 480915030df..05afde2052c 100644
--- a/src/backend/tsearch/wparser_def.c
+++ b/src/backend/tsearch/wparser_def.c
@@ -18,6 +18,7 @@
 #include <wctype.h>
 
 #include "commands/defrem.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "tsearch/ts_public.h"
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index cccc4a24c84..d629aa2a4ca 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_tablespace.h"
 #include "commands/tablespace.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "utils/acl.h"
diff --git a/src/backend/utils/adt/geo_ops.c b/src/backend/utils/adt/geo_ops.c
index 73324b91fe5..92114264518 100644
--- a/src/backend/utils/adt/geo_ops.c
+++ b/src/backend/utils/adt/geo_ops.c
@@ -29,6 +29,7 @@
 #include <float.h>
 #include <ctype.h>
 
+#include "ipc/interrupt.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
diff --git a/src/backend/utils/adt/jsonpath.c b/src/backend/utils/adt/jsonpath.c
index 7bfc18c9888..29660501674 100644
--- a/src/backend/utils/adt/jsonpath.c
+++ b/src/backend/utils/adt/jsonpath.c
@@ -64,6 +64,7 @@
 #include "postgres.h"
 
 #include "catalog/pg_type.h"
+#include "ipc/interrupt.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/adt/jsonpath_exec.c b/src/backend/utils/adt/jsonpath_exec.c
index 6cc2acb4254..d0d78aee783 100644
--- a/src/backend/utils/adt/jsonpath_exec.c
+++ b/src/backend/utils/adt/jsonpath_exec.c
@@ -62,6 +62,7 @@
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
diff --git a/src/backend/utils/adt/jsonpath_gram.y b/src/backend/utils/adt/jsonpath_gram.y
index f826697d098..afeac407158 100644
--- a/src/backend/utils/adt/jsonpath_gram.y
+++ b/src/backend/utils/adt/jsonpath_gram.y
@@ -18,8 +18,8 @@
 
 #include "catalog/pg_collation.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "jsonpath_internal.h"
-#include "miscadmin.h"
 #include "nodes/pg_list.h"
 #include "regex/regex.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/like.c b/src/backend/utils/adt/like.c
index 350bc07f210..f4b23ef641b 100644
--- a/src/backend/utils/adt/like.c
+++ b/src/backend/utils/adt/like.c
@@ -20,6 +20,7 @@
 #include <ctype.h>
 
 #include "catalog/pg_collation.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "utils/fmgrprotos.h"
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index cb23dfe9b95..81e8e94efd4 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -30,9 +30,9 @@
 #include "common/int.h"
 #include "common/int128.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/hyperloglog.h"
 #include "libpq/pqformat.h"
-#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/optimizer.h"
diff --git a/src/backend/utils/adt/oracle_compat.c b/src/backend/utils/adt/oracle_compat.c
index 5b0d098bd07..63adbfd410a 100644
--- a/src/backend/utils/adt/oracle_compat.c
+++ b/src/backend/utils/adt/oracle_compat.c
@@ -16,8 +16,8 @@
 #include "postgres.h"
 
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "utils/builtins.h"
 #include "utils/formatting.h"
 #include "utils/memutils.h"
diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c
index fd8b8676470..91570e81029 100644
--- a/src/backend/utils/adt/orderedsetaggs.c
+++ b/src/backend/utils/adt/orderedsetaggs.c
@@ -20,6 +20,7 @@
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
 #include "executor/executor.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 88de5c0481c..2b9fa67a52c 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -47,6 +47,7 @@
 #include "common/keywords.h"
 #include "executor/spi.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
diff --git a/src/backend/utils/adt/tsquery_rewrite.c b/src/backend/utils/adt/tsquery_rewrite.c
index aace2a4c7d6..95131f55fa2 100644
--- a/src/backend/utils/adt/tsquery_rewrite.c
+++ b/src/backend/utils/adt/tsquery_rewrite.c
@@ -16,6 +16,7 @@
 
 #include "catalog/pg_type.h"
 #include "executor/spi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "tsearch/ts_utils.h"
 #include "utils/builtins.h"
diff --git a/src/backend/utils/adt/tsvector_op.c b/src/backend/utils/adt/tsvector_op.c
index 53a9541e89f..9c689a40cdf 100644
--- a/src/backend/utils/adt/tsvector_op.c
+++ b/src/backend/utils/adt/tsvector_op.c
@@ -22,6 +22,7 @@
 #include "common/int.h"
 #include "executor/spi.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/qunique.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/adt/varlena.c b/src/backend/utils/adt/varlena.c
index 0c6d3ba4d22..cd774264d86 100644
--- a/src/backend/utils/adt/varlena.c
+++ b/src/backend/utils/adt/varlena.c
@@ -28,9 +28,9 @@
 #include "common/unicode_norm.h"
 #include "common/unicode_version.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "lib/hyperloglog.h"
 #include "libpq/pqformat.h"
-#include "miscadmin.h"
 #include "nodes/execnodes.h"
 #include "parser/scansup.h"
 #include "port/pg_bswap.h"
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 63dc36d4d91..f3cd242ce70 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -119,6 +119,7 @@
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
 #include "catalog/pg_constraint.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/procnumber.h"
 #include "storage/sinval.h"
diff --git a/src/backend/utils/init/globals.c b/src/backend/utils/init/globals.c
index bbd28d14d99..b41dd0a6928 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -19,6 +19,7 @@
 #include "postgres.h"
 
 #include "common/file_perm.h"
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"
diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c
index ddba5dc607c..ed54447d4a0 100644
--- a/src/backend/utils/misc/timeout.c
+++ b/src/backend/utils/misc/timeout.c
@@ -16,6 +16,7 @@
 
 #include <sys/time.h>
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "storage/latch.h"
 #include "utils/timeout.h"
diff --git a/src/backend/utils/mmgr/mcxt.c b/src/backend/utils/mmgr/mcxt.c
index 930fc457328..27b339a649e 100644
--- a/src/backend/utils/mmgr/mcxt.c
+++ b/src/backend/utils/mmgr/mcxt.c
@@ -39,6 +39,7 @@
 #include "postgres.h"
 
 #include "common/int.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "utils/memdebug.h"
diff --git a/src/backend/utils/sort/qsort_interruptible.c b/src/backend/utils/sort/qsort_interruptible.c
index f179b256248..fd23e7312d3 100644
--- a/src/backend/utils/sort/qsort_interruptible.c
+++ b/src/backend/utils/sort/qsort_interruptible.c
@@ -3,7 +3,7 @@
  */
 
 #include "postgres.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 
 #define ST_SORT qsort_interruptible
 #define ST_ELEMENT_TYPE_VOID
diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c
index c0e7527b9ca..0ceb3153c1e 100644
--- a/src/backend/utils/sort/tuplesort.c
+++ b/src/backend/utils/sort/tuplesort.c
@@ -102,7 +102,7 @@
 #include <limits.h>
 
 #include "commands/tablespace.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pg_trace.h"
 #include "port/pg_bitutils.h"
 #include "storage/shmem.h"
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index f9e2d95186a..50005c7f550 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -59,7 +59,7 @@
 #include "access/htup_details.h"
 #include "commands/tablespace.h"
 #include "executor/executor.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/buffile.h"
 #include "utils/memutils.h"
 #include "utils/resowner.h"
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c
index 614b7c1006b..93d52c07836 100644
--- a/src/backend/utils/time/combocid.c
+++ b/src/backend/utils/time/combocid.c
@@ -43,7 +43,7 @@
 
 #include "access/htup_details.h"
 #include "access/xact.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/shmem.h"
 #include "utils/combocid.h"
 #include "utils/hsearch.h"
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
index 259fa5554b6..00b92a7fea1 100644
--- a/src/common/scram-common.c
+++ b/src/common/scram-common.c
@@ -23,7 +23,7 @@
 #include "common/hmac.h"
 #include "common/scram-common.h"
 #ifndef FRONTEND
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #endif
 #include "port/pg_bswap.h"
 
diff --git a/src/include/executor/execScan.h b/src/include/executor/execScan.h
index 18b03235c3c..805a367b5fd 100644
--- a/src/include/executor/execScan.h
+++ b/src/include/executor/execScan.h
@@ -13,9 +13,9 @@
 #ifndef EXECSCAN_H
 #define EXECSCAN_H
 
-#include "miscadmin.h"
 #include "executor/executor.h"
 #include "executor/instrument.h"
+#include "ipc/interrupt.h"
 #include "nodes/execnodes.h"
 
 /*
diff --git a/src/include/ipc/interrupt.h b/src/include/ipc/interrupt.h
new file mode 100644
index 00000000000..b409ee18ddb
--- /dev/null
+++ b/src/include/ipc/interrupt.h
@@ -0,0 +1,192 @@
+/*-------------------------------------------------------------------------
+ *
+ * interrupt.h
+ *	  Inter-process interrupts
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/include/ipc/interrupt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef IPC_INTERRUPT_H
+#define IPC_INTERRUPT_H
+
+#include <signal.h>
+
+#include "port/atomics.h"
+#include "storage/procnumber.h"
+
+/*
+ * Include waiteventset.h for the WL_* flags. They're not needed her, but are
+ * needed which are needed by all callers of WaitInterrupt, so include it
+ * here.
+ *
+ * Note: InterruptMask is defined in waiteventset.h to avoid circular dependency
+ */
+#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.
+ *
+ * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
+ * where it is normally safe to accept a cancel or die interrupt.  In some
+ * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
+ * might sometimes be called in contexts that do *not* want to allow a cancel
+ * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
+ * allow code to ensure that no cancel or die interrupt will be accepted,
+ * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
+ * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
+ * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
+ *
+ * There is also a mechanism to prevent query cancel interrupts, while still
+ * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
+ * RESUME_CANCEL_INTERRUPTS().
+ *
+ * Note that ProcessInterrupts() has also acquired a number of tasks that
+ * do not necessarily cause a query-cancel-or-die response.  Hence, it's
+ * possible that it will just clear InterruptPending and return.
+ *
+ * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
+ * interrupt needs to be serviced, without trying to do so immediately.
+ * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
+ * which tells whether ProcessInterrupts is sure to clear the interrupt.
+ *
+ * Special mechanisms are used to let an interrupt be accepted when we are
+ * waiting for a lock or when we are waiting for command input (but, of
+ * course, only if the interrupt holdoff counter is zero).  See the
+ * related code for details.
+ *
+ * A lost connection is handled similarly, although the loss of connection
+ * does not raise a signal, but is detected when we fail to write to the
+ * socket. If there was a signal for a broken connection, we could make use of
+ * it by setting ClientConnectionLost in the signal handler.
+ *
+ * A related, but conceptually distinct, mechanism is the "critical section"
+ * mechanism.  A critical section not only holds off cancel/die interrupts,
+ * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
+ * --- that is, a system-wide reset is forced.  Needless to say, only really
+ * *critical* code should be marked as a critical section!	Currently, this
+ * mechanism is only used for XLOG-related code.
+ *
+ *****************************************************************************/
+
+/* in globals.c */
+/* these are marked volatile because they are set by signal handlers: */
+extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
+extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
+extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
+extern PGDLLIMPORT volatile 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
+ *****************************************************************************/
+
+/*
+ * 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
+ * if the HOLD_INTERRUPTS() is delaying the interrupt processing.
+ */
+#ifndef WIN32
+#define INTERRUPTS_PENDING_CONDITION() \
+	(unlikely(InterruptPending))
+#else
+#define INTERRUPTS_PENDING_CONDITION() \
+	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
+	 pgwin32_dispatch_queued_signals() : (void) 0, \
+	 unlikely(InterruptPending))
+#endif
+
+/*
+ * Can interrupts be processed in the current state, i.e. are the interrupts
+ * not prevented by the HOLD_INTERRUPTS() or a critical section?
+ */
+#define INTERRUPTS_CAN_BE_PROCESSED() \
+	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
+	 QueryCancelHoldoffCount == 0)
+
+/*
+ * Service an interrupt, if one is pending and it's safe to service it now.
+ *
+ * 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() \
+do { \
+	if (INTERRUPTS_PENDING_CONDITION()) \
+		ProcessInterrupts(); \
+} while(0)
+
+/* in tcop/postgres.c */
+extern void ProcessInterrupts(void);
+
+/*****************************************************************************
+ *		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;
+
+static inline void
+HOLD_INTERRUPTS(void)
+{
+	InterruptHoldoffCount++;
+}
+
+static inline void
+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--;
+}
+
+static inline void
+START_CRIT_SECTION(void)
+{
+	CritSectionCount++;
+}
+
+static inline void
+END_CRIT_SECTION(void)
+{
+	Assert(CritSectionCount > 0);
+	CritSectionCount--;
+}
+
+#endif							/* IPC_INTERRUPT_H */
diff --git a/src/include/libpq/libpq-be-fe-helpers.h b/src/include/libpq/libpq-be-fe-helpers.h
index cff68cd1c37..019e6c710e4 100644
--- a/src/include/libpq/libpq-be-fe-helpers.h
+++ b/src/include/libpq/libpq-be-fe-helpers.h
@@ -30,12 +30,13 @@
 #ifndef LIBPQ_BE_FE_HELPERS_H
 #define LIBPQ_BE_FE_HELPERS_H
 
+#include "ipc/interrupt.h"
 #include "libpq/libpq-be-fe.h"
 #include "miscadmin.h"
 #include "storage/fd.h"
 #include "storage/latch.h"
 #include "utils/timestamp.h"
-#include "utils/wait_event.h"
+#include "utils/wait_classes.h"
 
 
 static inline void libpqsrv_connect_prepare(void);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 7170a4bff98..5a1b6524673 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -23,8 +23,6 @@
 #ifndef MISCADMIN_H
 #define MISCADMIN_H
 
-#include <signal.h>
-
 #include "datatype/timestamp.h" /* for TimestampTz */
 #include "pgtime.h"				/* for pg_time_t */
 
@@ -32,132 +30,6 @@
 #define InvalidPid				(-1)
 
 
-/*****************************************************************************
- *	  System interrupt and critical section handling
- *
- * There are two types of interrupts that a running backend needs to accept
- * without messing up its state: QueryCancel (SIGINT) and ProcDie (SIGTERM).
- * In both cases, we need to be able to clean up the current transaction
- * gracefully, so we can't respond to the interrupt instantaneously ---
- * there's no guarantee that internal data structures would be self-consistent
- * if the code is interrupted at an arbitrary instant.  Instead, the signal
- * handlers set flags that are checked periodically during execution.
- *
- * The CHECK_FOR_INTERRUPTS() macro is called at strategically located spots
- * where it is normally safe to accept a cancel or die interrupt.  In some
- * cases, we invoke CHECK_FOR_INTERRUPTS() inside low-level subroutines that
- * might sometimes be called in contexts that do *not* want to allow a cancel
- * or die interrupt.  The HOLD_INTERRUPTS() and RESUME_INTERRUPTS() macros
- * allow code to ensure that no cancel or die interrupt will be accepted,
- * even if CHECK_FOR_INTERRUPTS() gets called in a subroutine.  The interrupt
- * will be held off until CHECK_FOR_INTERRUPTS() is done outside any
- * HOLD_INTERRUPTS() ... RESUME_INTERRUPTS() section.
- *
- * There is also a mechanism to prevent query cancel interrupts, while still
- * allowing die interrupts: HOLD_CANCEL_INTERRUPTS() and
- * RESUME_CANCEL_INTERRUPTS().
- *
- * Note that ProcessInterrupts() has also acquired a number of tasks that
- * do not necessarily cause a query-cancel-or-die response.  Hence, it's
- * possible that it will just clear InterruptPending and return.
- *
- * INTERRUPTS_PENDING_CONDITION() can be checked to see whether an
- * interrupt needs to be serviced, without trying to do so immediately.
- * Some callers are also interested in INTERRUPTS_CAN_BE_PROCESSED(),
- * which tells whether ProcessInterrupts is sure to clear the interrupt.
- *
- * Special mechanisms are used to let an interrupt be accepted when we are
- * waiting for a lock or when we are waiting for command input (but, of
- * course, only if the interrupt holdoff counter is zero).  See the
- * related code for details.
- *
- * A lost connection is handled similarly, although the loss of connection
- * does not raise a signal, but is detected when we fail to write to the
- * socket. If there was a signal for a broken connection, we could make use of
- * it by setting ClientConnectionLost in the signal handler.
- *
- * A related, but conceptually distinct, mechanism is the "critical section"
- * mechanism.  A critical section not only holds off cancel/die interrupts,
- * but causes any ereport(ERROR) or ereport(FATAL) to become ereport(PANIC)
- * --- that is, a system-wide reset is forced.  Needless to say, only really
- * *critical* code should be marked as a critical section!	Currently, this
- * mechanism is only used for XLOG-related code.
- *
- *****************************************************************************/
-
-/* in globals.c */
-/* these are marked volatile because they are set by signal handlers: */
-extern PGDLLIMPORT volatile sig_atomic_t InterruptPending;
-extern PGDLLIMPORT volatile sig_atomic_t QueryCancelPending;
-extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
-extern PGDLLIMPORT volatile 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;
-
-/* these are marked volatile because they are examined by signal handlers: */
-extern PGDLLIMPORT volatile uint32 InterruptHoldoffCount;
-extern PGDLLIMPORT volatile uint32 QueryCancelHoldoffCount;
-extern PGDLLIMPORT volatile uint32 CritSectionCount;
-
-/* in tcop/postgres.c */
-extern void ProcessInterrupts(void);
-
-/* Test whether an interrupt is pending */
-#ifndef WIN32
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(InterruptPending))
-#else
-#define INTERRUPTS_PENDING_CONDITION() \
-	(unlikely(UNBLOCKED_SIGNAL_QUEUE()) ? \
-	 pgwin32_dispatch_queued_signals() : (void) 0, \
-	 unlikely(InterruptPending))
-#endif
-
-/* Service interrupt, if one is pending and it's safe to service it now */
-#define CHECK_FOR_INTERRUPTS() \
-do { \
-	if (INTERRUPTS_PENDING_CONDITION()) \
-		ProcessInterrupts(); \
-} while(0)
-
-/* Is ProcessInterrupts() guaranteed to clear InterruptPending? */
-#define INTERRUPTS_CAN_BE_PROCESSED() \
-	(InterruptHoldoffCount == 0 && CritSectionCount == 0 && \
-	 QueryCancelHoldoffCount == 0)
-
-#define HOLD_INTERRUPTS()  (InterruptHoldoffCount++)
-
-#define RESUME_INTERRUPTS() \
-do { \
-	Assert(InterruptHoldoffCount > 0); \
-	InterruptHoldoffCount--; \
-} while(0)
-
-#define HOLD_CANCEL_INTERRUPTS()  (QueryCancelHoldoffCount++)
-
-#define RESUME_CANCEL_INTERRUPTS() \
-do { \
-	Assert(QueryCancelHoldoffCount > 0); \
-	QueryCancelHoldoffCount--; \
-} while(0)
-
-#define START_CRIT_SECTION()  (CritSectionCount++)
-
-#define END_CRIT_SECTION() \
-do { \
-	Assert(CritSectionCount > 0); \
-	CritSectionCount--; \
-} while(0)
-
-
 /*****************************************************************************
  *	  globals.h --															 *
  *****************************************************************************/
diff --git a/src/include/regex/regcustom.h b/src/include/regex/regcustom.h
index 8c4d5d8cc7c..ab038a12e8a 100644
--- a/src/include/regex/regcustom.h
+++ b/src/include/regex/regcustom.h
@@ -42,6 +42,7 @@
 #include <limits.h>
 #include <wctype.h>
 
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
 
 #include "miscadmin.h"			/* needed by rstacktoodeep */
diff --git a/src/include/utils/backend_status.h b/src/include/utils/backend_status.h
index a334e096e4a..8b13811f167 100644
--- a/src/include/utils/backend_status.h
+++ b/src/include/utils/backend_status.h
@@ -11,6 +11,7 @@
 #define BACKEND_STATUS_H
 
 #include "datatype/timestamp.h"
+#include "ipc/interrupt.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"			/* for BackendType */
 #include "storage/procnumber.h"
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index 3ca4f454895..62e7c736e87 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -15,6 +15,7 @@
 
 
 #include "common/hashfn_unstable.h"
+#include "ipc/interrupt.h"
 #include "lib/dshash.h"
 #include "lib/ilist.h"
 #include "pgstat.h"
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index 9ddb81d42b9..d7159d084f1 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -23,6 +23,7 @@
 #include "commands/trigger.h"
 #include "executor/spi.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "parser/parse_type.h"
 #include "storage/ipc.h"
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 65b0fd0790f..206250dc39a 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -26,8 +26,8 @@
 #include "executor/spi.h"
 #include "executor/tstoreReceiver.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/stringinfo_mb.h"
-#include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/optimizer.h"
diff --git a/src/port/pg_numa.c b/src/port/pg_numa.c
index 8954669273a..1df5ee3033f 100644
--- a/src/port/pg_numa.c
+++ b/src/port/pg_numa.c
@@ -16,7 +16,10 @@
 #include "c.h"
 #include <unistd.h>
 
-#include "miscadmin.h"
+#ifndef FRONTEND
+#include "postgres.h"
+#include "ipc/interrupt.h"
+#endif
 #include "port/pg_numa.h"
 
 /*
diff --git a/src/test/modules/injection_points/regress_injection.c b/src/test/modules/injection_points/regress_injection.c
index 0c3113eac2f..e090d5d28d1 100644
--- a/src/test/modules/injection_points/regress_injection.c
+++ b/src/test/modules/injection_points/regress_injection.c
@@ -16,7 +16,7 @@
 
 #include "access/table.h"
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "postmaster/autovacuum.h"
 #include "storage/procarray.h"
 #include "utils/rel.h"
diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c
index 66b6badb82f..64b443c5b4c 100644
--- a/src/test/modules/test_bitmapset/test_bitmapset.c
+++ b/src/test/modules/test_bitmapset/test_bitmapset.c
@@ -20,7 +20,7 @@
 #include "catalog/pg_type.h"
 #include "common/pg_prng.h"
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "nodes/bitmapset.h"
 #include "nodes/nodes.h"
 #include "nodes/pg_list.h"
diff --git a/src/test/modules/test_bloomfilter/test_bloomfilter.c b/src/test/modules/test_bloomfilter/test_bloomfilter.c
index df41066138c..9efa1bbdf2d 100644
--- a/src/test/modules/test_bloomfilter/test_bloomfilter.c
+++ b/src/test/modules/test_bloomfilter/test_bloomfilter.c
@@ -14,8 +14,8 @@
 
 #include "common/pg_prng.h"
 #include "fmgr.h"
+#include "ipc/interrupt.h"
 #include "lib/bloomfilter.h"
-#include "miscadmin.h"
 
 PG_MODULE_MAGIC;
 
diff --git a/src/test/modules/test_saslprep/test_saslprep.c b/src/test/modules/test_saslprep/test_saslprep.c
index 121212d4fa2..15a49e645ea 100644
--- a/src/test/modules/test_saslprep/test_saslprep.c
+++ b/src/test/modules/test_saslprep/test_saslprep.c
@@ -17,8 +17,8 @@
 #include "common/saslprep.h"
 #include "fmgr.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "utils/builtins.h"
 
 PG_MODULE_MAGIC;
diff --git a/src/test/modules/test_shm_mq/setup.c b/src/test/modules/test_shm_mq/setup.c
index 4f40a61e3d9..259c0c8253a 100644
--- a/src/test/modules/test_shm_mq/setup.c
+++ b/src/test/modules/test_shm_mq/setup.c
@@ -15,6 +15,7 @@
 
 #include "postgres.h"
 
+#include "ipc/interrupt.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
diff --git a/src/test/modules/test_shm_mq/test.c b/src/test/modules/test_shm_mq/test.c
index 0e55287e510..9f401d30176 100644
--- a/src/test/modules/test_shm_mq/test.c
+++ b/src/test/modules/test_shm_mq/test.c
@@ -14,7 +14,7 @@
 #include "postgres.h"
 
 #include "fmgr.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "pgstat.h"
 #include "storage/proc.h"
 #include "utils/wait_event.h"
diff --git a/src/test/modules/test_shm_mq/worker.c b/src/test/modules/test_shm_mq/worker.c
index e13c05ae5c7..1b22515ec5c 100644
--- a/src/test/modules/test_shm_mq/worker.c
+++ b/src/test/modules/test_shm_mq/worker.c
@@ -19,7 +19,7 @@
 
 #include "postgres.h"
 
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/proc.h"
diff --git a/src/test/modules/xid_wraparound/xid_wraparound.c b/src/test/modules/xid_wraparound/xid_wraparound.c
index ca25d7e0206..64353ab363f 100644
--- a/src/test/modules/xid_wraparound/xid_wraparound.c
+++ b/src/test/modules/xid_wraparound/xid_wraparound.c
@@ -15,7 +15,7 @@
 #include "postgres.h"
 
 #include "access/xact.h"
-#include "miscadmin.h"
+#include "ipc/interrupt.h"
 #include "storage/proc.h"
 #include "utils/xid8.h"
 
diff --git a/src/test/regress/regress.c b/src/test/regress/regress.c
index d5aafdf370c..321cdcd8b57 100644
--- a/src/test/regress/regress.c
+++ b/src/test/regress/regress.c
@@ -33,8 +33,8 @@
 #include "executor/spi.h"
 #include "foreign/foreign.h"
 #include "funcapi.h"
+#include "ipc/interrupt.h"
 #include "mb/pg_wchar.h"
-#include "miscadmin.h"
 #include "nodes/supportnodes.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/plancat.h"
-- 
2.47.3



  [text/x-patch] v13-0006-Rename-postmaster-interrupt.c-to-ipc-signal_hand.patch (20.1K, ../../[email protected]/7-v13-0006-Rename-postmaster-interrupt.c-to-ipc-signal_hand.patch)
  download | inline diff:
From 0176d059e4b3d020bbd6c7af7ecce6c1cede39a3 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Wed, 17 Jun 2026 09:57:50 +0300
Subject: [PATCH v13 6/9] Rename postmaster/interrupt.c to
 ipc/signal_handlers.c

postmaster/interrupt.c contains signal handling functions, so rename
it to ipc/signal_handlers.c, to better reflect its contents. The
"interrupt" term is now used to mean the concept around
CHECK_FOR_INTERRUPTS(). Interrupts can be raised by signal handlers,
but it's not the same thing.
---
 contrib/pg_prewarm/autoprewarm.c               |  2 +-
 contrib/pg_stash_advice/stashpersist.c         |  2 +-
 src/backend/Makefile                           |  1 +
 src/backend/commands/copyfrom.c                |  1 +
 src/backend/commands/vacuum.c                  |  2 +-
 src/backend/ipc/Makefile                       | 18 ++++++++++++++++++
 src/backend/ipc/meson.build                    |  5 +++++
 .../interrupt.c => ipc/signal_handlers.c}      |  8 ++++----
 src/backend/meson.build                        |  1 +
 src/backend/postmaster/Makefile                |  1 -
 src/backend/postmaster/autovacuum.c            |  2 +-
 src/backend/postmaster/bgwriter.c              |  2 +-
 src/backend/postmaster/checkpointer.c          |  2 +-
 src/backend/postmaster/meson.build             |  1 -
 src/backend/postmaster/pgarch.c                |  2 +-
 src/backend/postmaster/syslogger.c             |  2 +-
 src/backend/postmaster/walsummarizer.c         |  2 +-
 src/backend/postmaster/walwriter.c             |  2 +-
 .../replication/logical/applyparallelworker.c  |  2 +-
 src/backend/replication/logical/launcher.c     |  2 +-
 src/backend/replication/logical/sequencesync.c |  2 +-
 src/backend/replication/logical/slotsync.c     |  2 +-
 src/backend/replication/logical/worker.c       |  2 +-
 src/backend/replication/slot.c                 |  2 +-
 src/backend/replication/walreceiver.c          |  2 +-
 src/backend/replication/walsender.c            |  2 +-
 src/backend/storage/aio/method_worker.c        |  2 +-
 src/backend/tcop/postgres.c                    |  2 +-
 src/backend/utils/init/miscinit.c              |  2 +-
 .../interrupt.h => ipc/signal_handlers.h}      | 16 ++++++++--------
 src/test/modules/worker_spi/worker_spi.c       |  2 +-
 31 files changed, 60 insertions(+), 36 deletions(-)
 create mode 100644 src/backend/ipc/Makefile
 create mode 100644 src/backend/ipc/meson.build
 rename src/backend/{postmaster/interrupt.c => ipc/signal_handlers.c} (96%)
 rename src/include/{postmaster/interrupt.h => ipc/signal_handlers.h} (68%)

diff --git a/contrib/pg_prewarm/autoprewarm.c b/contrib/pg_prewarm/autoprewarm.c
index ba0bc8e6d4a..f11745f4755 100644
--- a/contrib/pg_prewarm/autoprewarm.c
+++ b/contrib/pg_prewarm/autoprewarm.c
@@ -30,9 +30,9 @@
 
 #include "access/relation.h"
 #include "access/xact.h"
+#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/buf_internals.h"
 #include "storage/dsm.h"
 #include "storage/dsm_registry.h"
diff --git a/contrib/pg_stash_advice/stashpersist.c b/contrib/pg_stash_advice/stashpersist.c
index 5bdf4bddaae..9e08300c541 100644
--- a/contrib/pg_stash_advice/stashpersist.c
+++ b/contrib/pg_stash_advice/stashpersist.c
@@ -14,10 +14,10 @@
 #include <sys/stat.h>
 
 #include "common/hashfn.h"
+#include "ipc/signal_handlers.h"
 #include "miscadmin.h"
 #include "pg_stash_advice.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/fd.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
diff --git a/src/backend/Makefile b/src/backend/Makefile
index 162d3f1f2a9..82156eff3c2 100644
--- a/src/backend/Makefile
+++ b/src/backend/Makefile
@@ -26,6 +26,7 @@ SUBDIRS = \
 	commands \
 	executor \
 	foreign \
+	ipc \
 	lib \
 	libpq \
 	main \
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index e6ba012e7d6..86e48df6536 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -21,6 +21,7 @@
 #include "postgres.h"
 
 #include <ctype.h>
+#include <signal.h>
 #include <unistd.h>
 #include <sys/stat.h>
 
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 21e74184b61..5cada287d79 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -43,11 +43,11 @@
 #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"
 #include "postmaster/bgworker_internals.h"
-#include "postmaster/interrupt.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
diff --git a/src/backend/ipc/Makefile b/src/backend/ipc/Makefile
new file mode 100644
index 00000000000..4120909ebbe
--- /dev/null
+++ b/src/backend/ipc/Makefile
@@ -0,0 +1,18 @@
+#-------------------------------------------------------------------------
+#
+# Makefile--
+#    Makefile for backend/ipc
+#
+# IDENTIFICATION
+#    src/backend/ipc/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/backend/ipc
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+OBJS = \
+	signal_handlers.o
+
+include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/ipc/meson.build b/src/backend/ipc/meson.build
new file mode 100644
index 00000000000..af7dae10b60
--- /dev/null
+++ b/src/backend/ipc/meson.build
@@ -0,0 +1,5 @@
+# Copyright (c) 2026, PostgreSQL Global Development Group
+
+backend_sources += files(
+  'signal_handlers.c',
+)
diff --git a/src/backend/postmaster/interrupt.c b/src/backend/ipc/signal_handlers.c
similarity index 96%
rename from src/backend/postmaster/interrupt.c
rename to src/backend/ipc/signal_handlers.c
index 20f7e34e204..44b39bc9ecf 100644
--- a/src/backend/postmaster/interrupt.c
+++ b/src/backend/ipc/signal_handlers.c
@@ -1,13 +1,13 @@
 /*-------------------------------------------------------------------------
  *
- * interrupt.c
- *	  Interrupt handling routines.
+ * signal_handlers.c
+ *	  Standard signal handlers.
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/backend/postmaster/interrupt.c
+ *	  src/backend/ipc/signal_handlers.c
  *
  *-------------------------------------------------------------------------
  */
@@ -17,8 +17,8 @@
 #include <unistd.h>
 
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "miscadmin.h"
-#include "postmaster/interrupt.h"
 #include "storage/ipc.h"
 #include "storage/latch.h"
 #include "storage/procsignal.h"
diff --git a/src/backend/meson.build b/src/backend/meson.build
index f737d799c61..b6af11a2125 100644
--- a/src/backend/meson.build
+++ b/src/backend/meson.build
@@ -15,6 +15,7 @@ subdir('catalog')
 subdir('commands')
 subdir('executor')
 subdir('foreign')
+subdir('ipc')
 subdir('jit')
 subdir('lib')
 subdir('libpq')
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index 55044b2bc6f..a478ddbab2e 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -20,7 +20,6 @@ OBJS = \
 	checkpointer.o \
 	datachecksum_state.o \
 	fork_process.o \
-	interrupt.o \
 	launch_backend.o \
 	pgarch.o \
 	pmchild.o \
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 31cad5df015..33d9a8ddcdf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -81,13 +81,13 @@
 #include "commands/vacuum.h"
 #include "common/int.h"
 #include "funcapi.h"
+#include "ipc/signal_handlers.h"
 #include "lib/ilist.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 6b7312b75d9..f199d92f57c 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -33,11 +33,11 @@
 
 #include "access/xlog.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio_subsys.h"
 #include "storage/buf_internals.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index bd73888747b..0be0b9009e8 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -45,12 +45,12 @@
 #include "catalog/pg_authid.h"
 #include "commands/defrem.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "port/atomics.h"
 #include "postmaster/auxprocess.h"
 #include "postmaster/bgwriter.h"
-#include "postmaster/interrupt.h"
 #include "replication/syncrep.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/postmaster/meson.build b/src/backend/postmaster/meson.build
index 6cba23bbeef..c8baef330f6 100644
--- a/src/backend/postmaster/meson.build
+++ b/src/backend/postmaster/meson.build
@@ -8,7 +8,6 @@ backend_sources += files(
   'checkpointer.c',
   'datachecksum_state.c',
   'fork_process.c',
-  'interrupt.c',
   'launch_backend.c',
   'pgarch.c',
   'pmchild.c',
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 8098e29ff65..f9db35c9285 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -34,11 +34,11 @@
 #include "archive/archive_module.h"
 #include "archive/shell_archive.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "lib/binaryheap.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/pgarch.h"
 #include "storage/condition_variable.h"
 #include "storage/aio_subsys.h"
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index b399adcce75..64d7511ee5e 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -32,6 +32,7 @@
 #include <sys/time.h>
 
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "lib/stringinfo.h"
 #include "libpq/pqsignal.h"
 #include "miscadmin.h"
@@ -39,7 +40,6 @@
 #include "pgstat.h"
 #include "pgtime.h"
 #include "port/pg_bitutils.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "storage/dsm.h"
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index ea6afab8d69..ed080cdac49 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -33,10 +33,10 @@
 #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"
-#include "postmaster/interrupt.h"
 #include "postmaster/walsummarizer.h"
 #include "replication/walreceiver.h"
 #include "storage/aio_subsys.h"
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index d32297fc259..25745ed30b3 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -47,9 +47,9 @@
 #include "access/xlog.h"
 #include "libpq/pqsignal.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "storage/aio_subsys.h"
 #include "storage/bufmgr.h"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index 6e5f3ab123f..adf7766e8c7 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -158,10 +158,10 @@
 #include "postgres.h"
 
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "libpq/pqmq.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
 #include "replication/origin.h"
diff --git a/src/backend/replication/logical/launcher.c b/src/backend/replication/logical/launcher.c
index 920c5a3de99..962238b8202 100644
--- a/src/backend/replication/logical/launcher.c
+++ b/src/backend/replication/logical/launcher.c
@@ -27,9 +27,9 @@
 #include "funcapi.h"
 #include "ipc/interrupt.h"
 #include "lib/dshash.h"
+#include "ipc/signal_handlers.h"
 #include "pgstat.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
 #include "replication/slot.h"
diff --git a/src/backend/replication/logical/sequencesync.c b/src/backend/replication/logical/sequencesync.c
index 1cfd9a36b34..b722288ae39 100644
--- a/src/backend/replication/logical/sequencesync.c
+++ b/src/backend/replication/logical/sequencesync.c
@@ -57,8 +57,8 @@
 #include "catalog/pg_subscription_rel.h"
 #include "commands/sequence.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicalworker.h"
 #include "replication/worker_internal.h"
 #include "storage/lwlock.h"
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 489c115f45f..96592ce4514 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -64,9 +64,9 @@
 #include "access/xlogrecovery.h"
 #include "catalog/pg_database.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
 #include "replication/snapbuild.h"
diff --git a/src/backend/replication/logical/worker.c b/src/backend/replication/logical/worker.c
index 7799266c614..7219b80c0d4 100644
--- a/src/backend/replication/logical/worker.c
+++ b/src/backend/replication/logical/worker.c
@@ -263,6 +263,7 @@
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "executor/execPartition.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqformat.h"
 #include "miscadmin.h"
 #include "optimizer/optimizer.h"
@@ -270,7 +271,6 @@
 #include "pgstat.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/walwriter.h"
 #include "replication/conflict.h"
 #include "replication/logicallauncher.h"
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 010657fb400..8c61583fbfd 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -45,8 +45,8 @@
 #include "common/file_utils.h"
 #include "common/string.h"
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/logicallauncher.h"
 #include "replication/slotsync.h"
 #include "replication/slot.h"
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index d7804a73454..9abc9817362 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -61,11 +61,11 @@
 #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"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "replication/walreceiver.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 795393b0be0..e5ac08eb179 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -65,13 +65,13 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "funcapi.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
 #include "nodes/replnodes.h"
 #include "pgstat.h"
-#include "postmaster/interrupt.h"
 #include "replication/decode.h"
 #include "replication/logical.h"
 #include "replication/slotsync.h"
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index abffeefc2c1..76635e3c41c 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -31,10 +31,10 @@
 #include <limits.h>
 
 #include "ipc/interrupt.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/pqsignal.h"
 #include "port/pg_bitutils.h"
 #include "postmaster/auxprocess.h"
-#include "postmaster/interrupt.h"
 #include "storage/aio.h"
 #include "storage/aio_internal.h"
 #include "storage/aio_subsys.h"
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 80449b0b3cd..1c926928e9c 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/signal_handlers.h"
 #include "jit/jit.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
@@ -55,7 +56,6 @@
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "port/pg_getopt_ctx.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/logicalworker.h"
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 5343d4219ae..f4992ff1622 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -32,13 +32,13 @@
 #include "access/parallel.h"
 #include "catalog/pg_authid.h"
 #include "common/file_perm.h"
+#include "ipc/signal_handlers.h"
 #include "libpq/libpq.h"
 #include "libpq/pqsignal.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
-#include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
 #include "replication/slotsync.h"
 #include "storage/fd.h"
diff --git a/src/include/postmaster/interrupt.h b/src/include/ipc/signal_handlers.h
similarity index 68%
rename from src/include/postmaster/interrupt.h
rename to src/include/ipc/signal_handlers.h
index e1482626dfd..a499a271538 100644
--- a/src/include/postmaster/interrupt.h
+++ b/src/include/ipc/signal_handlers.h
@@ -1,23 +1,23 @@
 /*-------------------------------------------------------------------------
  *
- * interrupt.h
- *	  Interrupt handling routines.
+ * signal_handlers.h
+ *	  Standard signal handling routines.
  *
- * Responses to interrupts are fairly varied and many types of backends
- * have their own implementations, but we provide a few generic things
- * here to facilitate code reuse.
+ * Responses to signals are fairly varied and many types of backends have
+ * their own implementations, but we provide a few generic things here to
+ * facilitate code reuse.
  *
  * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  src/include/postmaster/interrupt.h
+ *	  src/include/ipc/signal_handlers.h
  *
  *-------------------------------------------------------------------------
  */
 
-#ifndef INTERRUPT_H
-#define INTERRUPT_H
+#ifndef SIGNAL_HANDLERS_H
+#define SIGNAL_HANDLERS_H
 
 #include <signal.h>
 
diff --git a/src/test/modules/worker_spi/worker_spi.c b/src/test/modules/worker_spi/worker_spi.c
index b635a486349..bd9002bf41d 100644
--- a/src/test/modules/worker_spi/worker_spi.c
+++ b/src/test/modules/worker_spi/worker_spi.c
@@ -23,9 +23,9 @@
 #include "postgres.h"
 
 /* These are always necessary for a bgworker */
+#include "ipc/signal_handlers.h"
 #include "miscadmin.h"
 #include "postmaster/bgworker.h"
-#include "postmaster/interrupt.h"
 #include "storage/latch.h"
 
 /* these headers are used by this particular worker's code */
-- 
2.47.3



  [text/x-patch] v13-0007-Remove-dependency-on-utils-resowner.h-just-for-R.patch (2.2K, ../../[email protected]/8-v13-0007-Remove-dependency-on-utils-resowner.h-just-for-R.patch)
  download | inline diff:
From a6257cff0c11ba53af85810efb9d119a78b94ca5 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 16 Jun 2026 21:56:45 +0300
Subject: [PATCH v13 7/9] Remove dependency on utils/resowner.h just for
 ResourceOwner typedef

---
 src/backend/executor/nodeAppend.c   | 1 +
 src/backend/postmaster/auxprocess.c | 1 +
 src/include/storage/waiteventset.h  | 5 ++---
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/backend/executor/nodeAppend.c b/src/backend/executor/nodeAppend.c
index 2c67218912e..c6ab9d5e19e 100644
--- a/src/backend/executor/nodeAppend.c
+++ b/src/backend/executor/nodeAppend.c
@@ -66,6 +66,7 @@
 #include "pgstat.h"
 #include "storage/latch.h"
 #include "storage/lwlock.h"
+#include "utils/resowner.h"
 #include "utils/wait_event.h"
 
 /* Shared state for parallel-aware Append. */
diff --git a/src/backend/postmaster/auxprocess.c b/src/backend/postmaster/auxprocess.c
index ab2af862d69..ae7505b9903 100644
--- a/src/backend/postmaster/auxprocess.c
+++ b/src/backend/postmaster/auxprocess.c
@@ -25,6 +25,7 @@
 #include "storage/procsignal.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
+#include "utils/resowner.h"
 #include "utils/wait_event.h"
 
 
diff --git a/src/include/storage/waiteventset.h b/src/include/storage/waiteventset.h
index 5341267f0a0..b18e4815bfc 100644
--- a/src/include/storage/waiteventset.h
+++ b/src/include/storage/waiteventset.h
@@ -25,8 +25,6 @@
 #ifndef WAITEVENTSET_H
 #define WAITEVENTSET_H
 
-#include "utils/resowner.h"
-
 /*
  * Bitmasks for events that may wake-up WaitLatch(), WaitLatchOrSocket(), or
  * WaitEventSetWait().
@@ -71,13 +69,14 @@ typedef struct WaitEvent
 typedef struct WaitEventSet WaitEventSet;
 
 struct Latch;
+struct ResourceOwnerData;
 
 /*
  * prototypes for functions in waiteventset.c
  */
 extern void InitializeWaitEventSupport(void);
 
-extern WaitEventSet *CreateWaitEventSet(ResourceOwner resowner, int nevents);
+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,
-- 
2.47.3



  [text/x-patch] v13-0008-Remove-unnecessary-include-from-tcopprot.h.patch (4.3K, ../../[email protected]/9-v13-0008-Remove-unnecessary-include-from-tcopprot.h.patch)
  download | inline diff:
From 6ada0c11e01c31f6fe0f5ff8e79c211819e0fe49 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
Date: Tue, 16 Jun 2026 22:38:34 +0300
Subject: [PATCH v13 8/9] Remove unnecessary #include from tcopprot.h

It was made unnecessary in commit 17f51ea818. Add corresponding #include
to source files that were relying on the indirect #include.
---
 src/backend/access/transam/parallel.c                 | 1 +
 src/backend/commands/repack_worker.c                  | 1 +
 src/backend/libpq/pqmq.c                              | 1 +
 src/backend/replication/logical/applyparallelworker.c | 1 +
 src/backend/replication/logical/slotsync.c            | 1 +
 src/backend/replication/walsender.c                   | 1 +
 src/backend/storage/aio/method_worker.c               | 1 +
 src/include/tcop/tcopprot.h                           | 1 -
 8 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/transam/parallel.c b/src/backend/access/transam/parallel.c
index 5c92e05ad86..afb2f92aec4 100644
--- a/src/backend/access/transam/parallel.c
+++ b/src/backend/access/transam/parallel.c
@@ -39,6 +39,7 @@
 #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"
diff --git a/src/backend/commands/repack_worker.c b/src/backend/commands/repack_worker.c
index 5888a5a1fe1..04d9ff0d83d 100644
--- a/src/backend/commands/repack_worker.c
+++ b/src/backend/commands/repack_worker.c
@@ -26,6 +26,7 @@
 #include "replication/snapbuild.h"
 #include "storage/ipc.h"
 #include "storage/proc.h"
+#include "storage/procsignal.h"
 #include "tcop/tcopprot.h"
 #include "utils/memutils.h"
 
diff --git a/src/backend/libpq/pqmq.c b/src/backend/libpq/pqmq.c
index c11433968c8..c55b70e5636 100644
--- a/src/backend/libpq/pqmq.c
+++ b/src/backend/libpq/pqmq.c
@@ -22,6 +22,7 @@
 #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"
diff --git a/src/backend/replication/logical/applyparallelworker.c b/src/backend/replication/logical/applyparallelworker.c
index adf7766e8c7..26eb2bb6bfe 100644
--- a/src/backend/replication/logical/applyparallelworker.c
+++ b/src/backend/replication/logical/applyparallelworker.c
@@ -170,6 +170,7 @@
 #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"
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 96592ce4514..20828e42499 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -74,6 +74,7 @@
 #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"
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index e5ac08eb179..b1ad492ae35 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -88,6 +88,7 @@
 #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"
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index 76635e3c41c..94156f26eb4 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -44,6 +44,7 @@
 #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"
diff --git a/src/include/tcop/tcopprot.h b/src/include/tcop/tcopprot.h
index 5bc5bcfb20d..0d4aebfd543 100644
--- a/src/include/tcop/tcopprot.h
+++ b/src/include/tcop/tcopprot.h
@@ -16,7 +16,6 @@
 
 #include "nodes/params.h"
 #include "nodes/plannodes.h"
-#include "storage/procsignal.h"
 #include "utils/guc.h"
 #include "utils/queryenvironment.h"
 
-- 
2.47.3



  [text/x-patch] v13-0009-Replace-Latches-with-Interrupts.patch (495.1K, ../../[email protected]/10-v13-0009-Replace-Latches-with-Interrupts.patch)
  download | inline diff:
From c6590a9631a7d0efcc0cd6ea1757feb8ab4e1708 Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <[email protected]>
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/[email protected]

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 <[email protected]>
Reviewed-by: Andres Freund <[email protected]>
Discussion: https://www.postgresql.org/message-id/[email protected]
Discussion: https://www.postgresql.org/message-id/[email protected]
---
 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



^ permalink  raw  reply  [nested|flat] 22+ messages in thread


end of thread, other threads:[~2026-06-24 20:25 UTC | newest]

Thread overview: 22+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-11-11 08:01 [PATCH v38 04/11] Add Incremental View Maintenance support to pg_dump Yugo Nagata <[email protected]>
2024-12-02 10:42 Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2024-12-02 14:39 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2025-01-28 17:01   ` Re: Interrupts vs signals Andres Freund <[email protected]>
2025-01-28 21:15     ` Re: Interrupts vs signals Andres Freund <[email protected]>
2025-01-28 22:04       ` Re: Interrupts vs signals Thomas Munro <[email protected]>
2025-02-28 20:24     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2025-03-05 19:25       ` Re: Interrupts vs signals Andres Freund <[email protected]>
2025-03-05 23:28         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2025-03-06 00:47     ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2025-03-06 16:43       ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2025-07-15 15:50         ` Re: Interrupts vs signals Andres Freund <[email protected]>
2025-07-23 07:42         ` Re: Interrupts vs signals Joel Jacobson <[email protected]>
2025-07-24 22:04           ` Re: Interrupts vs signals Joel Jacobson <[email protected]>
2025-07-15 16:50       ` Re: Interrupts vs signals Andres Freund <[email protected]>
2026-01-29 01:05         ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2026-02-13 17:11           ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2026-02-14 21:56             ` Re: Interrupts vs signals Andres Freund <[email protected]>
2026-02-18 00:11               ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2026-02-20 14:22                 ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2026-03-06 14:30                   ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>
2026-06-24 20:25                   ` Re: Interrupts vs signals Heikki Linnakangas <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox