From e7c6913c1e392e6a41e4f1886897cdbb479f7254 Mon Sep 17 00:00:00 2001
From: Arseniy Mukhin <arseniy.mukhin.dev@gmail.com>
Date: Wed, 20 May 2026 12:45:54 +0300
Subject: [PATCH] LISTEN/NOTIFY test - untriggered notification

---
 src/backend/commands/async.c                  |  2 +
 src/test/modules/injection_points/Makefile    |  3 +-
 ...notify-listen-untriggered-notification.out | 45 ++++++++++++++
 src/test/modules/injection_points/meson.build |  1 +
 ...otify-listen-untriggered-notification.spec | 60 +++++++++++++++++++
 5 files changed, 110 insertions(+), 1 deletion(-)
 create mode 100644 src/test/modules/injection_points/expected/async-notify-listen-untriggered-notification.out
 create mode 100644 src/test/modules/injection_points/specs/async-notify-listen-untriggered-notification.spec

diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index f1bd278bd19..fdb43321e04 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -2287,6 +2287,8 @@ SignalBackends(void)
 	 */
 	count = 0;
 
+	INJECTION_POINT("async-notify-signal-backends-start", NULL);
+
 	LWLockAcquire(NotifyQueueLock, LW_EXCLUSIVE);
 
 	/* Scan each channel name that we notified in this transaction */
diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile
index 665e7852d52..f53e331c56e 100644
--- a/src/test/modules/injection_points/Makefile
+++ b/src/test/modules/injection_points/Makefile
@@ -22,7 +22,8 @@ ISOLATION = basic \
 	    repack_temporal_multirange \
 	    repack_toast \
 	    syscache-update-pruned \
-	    heap_lock_update
+	    heap_lock_update \
+        async-notify-listen-untriggered-notification
 
 # some isolation tests require wal_level=replica
 ISOLATION_OPTS = --temp-config $(top_srcdir)/src/test/modules/injection_points/extra.conf
diff --git a/src/test/modules/injection_points/expected/async-notify-listen-untriggered-notification.out b/src/test/modules/injection_points/expected/async-notify-listen-untriggered-notification.out
new file mode 100644
index 00000000000..dd77a8182b4
--- /dev/null
+++ b/src/test/modules/injection_points/expected/async-notify-listen-untriggered-notification.out
@@ -0,0 +1,45 @@
+Parsed test spec with 4 sessions
+
+starting permutation: listen notify2 notify wake wake_up_signal_backends_start check check check notify check
+step listen: LISTEN race; <waiting ...>
+step notify2: NOTIFY ch2, 'ch2_payload'; <waiting ...>
+step notify: NOTIFY race, 'payload';
+step wake: 
+	SELECT FROM injection_points_detach('async-notify-before-listen-commit');
+	SELECT FROM injection_points_wakeup('async-notify-before-listen-commit');
+ <waiting ...>
+step listen: <... completed>
+listener: NOTIFY "race" with payload "payload" from n1
+step wake: <... completed>
+step wake_up_signal_backends_start: 
+	SELECT FROM injection_points_detach('async-notify-signal-backends-start');
+	SELECT FROM injection_points_wakeup('async-notify-signal-backends-start');
+ <waiting ...>
+step notify2: <... completed>
+step check: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+step wake_up_signal_backends_start: <... completed>
+step check: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+step check: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+step notify: NOTIFY race, 'payload';
+step check: SELECT 1 AS x;
+x
+-
+1
+(1 row)
+
+listener: NOTIFY "race" with payload "payload" from n1
diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build
index 7fb8de3d0f8..cf65c4a4817 100644
--- a/src/test/modules/injection_points/meson.build
+++ b/src/test/modules/injection_points/meson.build
@@ -54,6 +54,7 @@ tests += {
       'repack_toast',
       'syscache-update-pruned',
       'heap_lock_update',
+      'async-notify-listen-untriggered-notification',
     ],
     'runningcheck': false, # see syscache-update-pruned
     # Some tests wait for all snapshots, so avoid parallel execution
diff --git a/src/test/modules/injection_points/specs/async-notify-listen-untriggered-notification.spec b/src/test/modules/injection_points/specs/async-notify-listen-untriggered-notification.spec
new file mode 100644
index 00000000000..d57d46957c4
--- /dev/null
+++ b/src/test/modules/injection_points/specs/async-notify-listen-untriggered-notification.spec
@@ -0,0 +1,60 @@
+# Test LISTEN/NOTIFY untriggered notification
+#
+# -----------------------------------------------------------------------------------------------------
+# l    | start listening                             | wakeup, set listen=true and
+#      | sleep before set listen=true                | start waiting for new messages
+# -----------------------------------------------------------------------------------------------------
+# n2       | add unrelated message,                      | advance listener over our unrelated message
+#          | advance to signal_backends start            | listener pos now is right before related message
+#                                                        | but listener doesn't know that
+#                                                        | there is something to read
+# -----------------------------------------------------------------------------------------------------
+# n1               | send related message
+#                  | don't signal as listen=false,
+#                  | don't direct advance as n2 message is in between our message and listener position
+# -----------------------------------------------------------------------------------------------------
+
+setup
+{
+	CREATE EXTENSION injection_points;
+}
+
+teardown
+{
+	DROP EXTENSION injection_points;
+}
+
+session listener
+setup
+{
+	SELECT FROM injection_points_set_local();
+	SELECT FROM injection_points_attach('async-notify-before-listen-commit', 'wait');
+}
+step listen	{ LISTEN race; }
+step check	{ SELECT 1 AS x; }
+teardown	{ UNLISTEN *; }
+
+session n1
+step notify	{ NOTIFY race, 'payload'; }
+
+session n2
+setup
+{
+	SELECT FROM injection_points_set_local();
+	SELECT FROM injection_points_attach('async-notify-signal-backends-start', 'wait');
+}
+step notify2	{ NOTIFY ch2, 'ch2_payload'; }
+
+session ctrl
+step wake
+{
+	SELECT FROM injection_points_detach('async-notify-before-listen-commit');
+	SELECT FROM injection_points_wakeup('async-notify-before-listen-commit');
+}
+step wake_up_signal_backends_start
+{
+	SELECT FROM injection_points_detach('async-notify-signal-backends-start');
+	SELECT FROM injection_points_wakeup('async-notify-signal-backends-start');
+}
+
+permutation listen notify2 notify wake(listen) wake_up_signal_backends_start(notify2) check check check notify check
-- 
2.43.0

