v10-0005-Introduce-PendingInterrupts-struct-with-separate.patch

text/x-patch

Filename: v10-0005-Introduce-PendingInterrupts-struct-with-separate.patch
Type: text/x-patch
Part: 4
Message: Re: Interrupts vs signals

Patch

Format: format-patch
Series: patch v10-0005
Subject: Introduce PendingInterrupts struct with separate 'flags' field
File+
src/backend/ipc/interrupt.c 129 54
src/backend/storage/ipc/waiteventset.c 23 14
src/include/ipc/interrupt.h 117 19
src/include/storage/proc.h 1 1
src/tools/pgindent/typedefs.list 1 0
From 6e5b9fcd7049314a113961447cc3d1545e19133e Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakangas@iki.fi>
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