From 07e7a7fe83540f0918290ae581c72d5a287b70aa Mon Sep 17 00:00:00 2001
From: Vlad Lesin <vladlesin@gmail.com>
Date: Fri, 15 May 2026 00:20:17 +0300
Subject: [PATCH 2/5] injection_points: wake every matching waiter in
 injection_points_wakeup()

injection_points_wakeup() previously bumped only the first matching
slot's counter and broke out of the loop.  ConditionVariableBroadcast
already wakes every CV waiter, but a waiter whose own counter was not
bumped re-checks its slot under inj_state->lock and goes right back
to sleep, so a single wakeup could not release multiple backends
parked on the same point name.

Bump every matching slot's counter in the same critical section, and
preserve the existing "could not find injection point %s to wake up"
error path via a found boolean.  All existing in-tree callers attach
one waiter per name, so waking "all of 1" is observationally
identical to "first of 1" -- no behavior change for them.

The wake-only-first behavior is undocumented.  The function was
introduced by commit 37b369dc67b ("injection_points: Add wait and
wakeup of processes"), and neither that commit's log entry nor the
in-code comments justify it.  Subsequent commits did not revisit
this loop (git log -L on injection_points_wakeup since 37b369dc67b
returns only the introducing commit).  Every in-tree caller happens
to attach exactly one waiter per name, so the limitation has never
been exercised and the regression test added in a following commit
is the first scenario that depends on the corrected semantics.
---
 .../injection_points/injection_points.c       | 23 +++++++++++--------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index fb7a7d477cc..35f01fc360b 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -439,29 +439,32 @@ Datum
 injection_points_wakeup(PG_FUNCTION_ARGS)
 {
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
-	int			index = -1;
+	bool		found = false;
 
 	if (inj_state == NULL)
 		injection_init_shmem();
 
-	/* First bump the wait counter for the injection point to wake up */
+	/*
+	 * Bump the wait counter for every slot waiting on this name.
+	 * ConditionVariableBroadcast wakes every CV waiter, but a waiter whose
+	 * own counter was not bumped re-checks its slot and goes back to sleep --
+	 * so we must bump them all to release multiple backends parked on the
+	 * same point name.
+	 */
 	SpinLockAcquire(&inj_state->lock);
 	for (int i = 0; i < INJ_MAX_WAIT; i++)
 	{
 		if (strcmp(name, inj_state->name[i]) == 0)
 		{
-			index = i;
-			break;
+			inj_state->wait_counts[i]++;
+			found = true;
 		}
 	}
-	if (index < 0)
-	{
-		SpinLockRelease(&inj_state->lock);
-		elog(ERROR, "could not find injection point %s to wake up", name);
-	}
-	inj_state->wait_counts[index]++;
 	SpinLockRelease(&inj_state->lock);
 
+	if (!found)
+		elog(ERROR, "could not find injection point %s to wake up", name);
+
 	/* And broadcast the change to the waiters */
 	ConditionVariableBroadcast(&inj_state->wait_point);
 	PG_RETURN_VOID();
-- 
2.43.0

