From d5d3f0bb997a5698ffdb0fa89131d1683460a15c Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 27 Jun 2025 18:23:45 +0900
Subject: [PATCH] Make injection point facilities consistent with HEAD

This brings the set of features that exist for injection points on par
with HEAD, for branch REL_17_STABLE.
---
 src/include/utils/injection_point.h           | 20 +++--
 src/backend/access/gin/ginbtree.c             |  6 +-
 src/backend/access/heap/heapam.c              |  2 +-
 src/backend/access/index/genam.c              |  2 +-
 src/backend/access/transam/xlog.c             |  4 +-
 src/backend/commands/indexcmds.c              |  4 +-
 src/backend/commands/vacuum.c                 | 12 +--
 src/backend/replication/logical/logical.c     |  2 +-
 src/backend/tcop/postgres.c                   |  6 +-
 src/backend/utils/cache/catcache.c            |  2 +-
 src/backend/utils/cache/inval.c               |  2 +-
 src/backend/utils/misc/injection_point.c      | 59 ++++++++++--
 .../expected/injection_points.out             | 90 +++++++++++++++++++
 .../injection_points--1.0.sql                 | 26 +++++-
 .../injection_points/injection_points.c       | 81 ++++++++++++++---
 .../injection_points/sql/injection_points.sql | 19 ++++
 doc/src/sgml/xfunc.sgml                       | 54 ++++++++++-
 17 files changed, 344 insertions(+), 47 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index 6e417cedc60b..1a7c30677054 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -12,19 +12,26 @@
 #define INJECTION_POINT_H
 
 /*
- * Injections points require --enable-injection-points.
+ * Injection points require --enable-injection-points.
  */
 #ifdef USE_INJECTION_POINTS
-#define INJECTION_POINT(name) InjectionPointRun(name)
+#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
+#define INJECTION_POINT(name, arg) InjectionPointRun(name, arg)
+#define INJECTION_POINT_CACHED(name, arg) InjectionPointCached(name, arg)
+#define IS_INJECTION_POINT_ATTACHED(name) IsInjectionPointAttached(name)
 #else
-#define INJECTION_POINT(name) ((void) name)
+#define INJECTION_POINT_LOAD(name) ((void) name)
+#define INJECTION_POINT(name, arg) ((void) name)
+#define INJECTION_POINT_CACHED(name, arg) ((void) name)
+#define IS_INJECTION_POINT_ATTACHED(name) (false)
 #endif
 
 /*
  * Typedef for callback function launched by an injection point.
  */
 typedef void (*InjectionPointCallback) (const char *name,
-										const void *private_data);
+										const void *private_data,
+										void *arg);
 
 extern Size InjectionPointShmemSize(void);
 extern void InjectionPointShmemInit(void);
@@ -34,7 +41,10 @@ extern void InjectionPointAttach(const char *name,
 								 const char *function,
 								 const void *private_data,
 								 int private_data_size);
-extern void InjectionPointRun(const char *name);
+extern void InjectionPointLoad(const char *name);
+extern void InjectionPointRun(const char *name, void *arg);
+extern void InjectionPointCached(const char *name, void *arg);
+extern bool IsInjectionPointAttached(const char *name);
 extern bool InjectionPointDetach(const char *name);
 
 #ifdef EXEC_BACKEND
diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c
index b7a5013896ad..35d91f1e2387 100644
--- a/src/backend/access/gin/ginbtree.c
+++ b/src/backend/access/gin/ginbtree.c
@@ -685,9 +685,9 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
 
 #ifdef USE_INJECTION_POINTS
 		if (GinPageIsLeaf(BufferGetPage(stack->buffer)))
-			INJECTION_POINT("gin-leave-leaf-split-incomplete");
+			INJECTION_POINT("gin-leave-leaf-split-incomplete", NULL);
 		else
-			INJECTION_POINT("gin-leave-internal-split-incomplete");
+			INJECTION_POINT("gin-leave-internal-split-incomplete", NULL);
 #endif
 
 		/* search parent to lock */
@@ -778,7 +778,7 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack,
 static void
 ginFinishOldSplit(GinBtree btree, GinBtreeStack *stack, GinStatsData *buildStats, int access)
 {
-	INJECTION_POINT("gin-finish-incomplete-split");
+	INJECTION_POINT("gin-finish-incomplete-split", NULL);
 	elog(DEBUG1, "finishing incomplete split of block %u in gin index \"%s\"",
 		 stack->blkno, RelationGetRelationName(btree->index));
 
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index cce38f482bd6..01a6ec75d6f5 100644
--- a/src/backend/access/heap/heapam.c
+++ b/src/backend/access/heap/heapam.c
@@ -3294,7 +3294,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
 	interesting_attrs = bms_add_members(interesting_attrs, id_attrs);
 
 	block = ItemPointerGetBlockNumber(otid);
-	INJECTION_POINT("heap_update-before-pin");
+	INJECTION_POINT("heap_update-before-pin", NULL);
 	buffer = ReadBuffer(relation, block);
 	page = BufferGetPage(buffer);
 
diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c
index b123acc5a609..6c75ab7b304b 100644
--- a/src/backend/access/index/genam.c
+++ b/src/backend/access/index/genam.c
@@ -841,7 +841,7 @@ systable_inplace_update_begin(Relation relation,
 			elog(ERROR, "giving up after too many tries to overwrite row");
 
 		memcpy(mutable_key, key, sizeof(ScanKeyData) * nkeys);
-		INJECTION_POINT("inplace-before-pin");
+		INJECTION_POINT("inplace-before-pin", NULL);
 		scan = systable_beginscan(relation, indexId, indexOK, snapshot,
 								  nkeys, mutable_key);
 		oldtup = systable_getnext(scan);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index a00786d40c80..ff5ed4c113b0 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -7314,7 +7314,7 @@ CreateCheckPoint(int flags)
 		UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr);
 
 #ifdef USE_INJECTION_POINTS
-	INJECTION_POINT("checkpoint-before-old-wal-removal");
+	INJECTION_POINT("checkpoint-before-old-wal-removal", NULL);
 #endif
 
 	/*
@@ -7717,7 +7717,7 @@ CreateRestartPoint(int flags)
 	 * This location needs to be after CheckPointGuts() to ensure that some
 	 * work has already happened during this checkpoint.
 	 */
-	INJECTION_POINT("create-restart-point");
+	INJECTION_POINT("create-restart-point", NULL);
 
 	/*
 	 * Remember the prior checkpoint's redo ptr for
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 75edb3697b30..3fae544c11a1 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -3795,9 +3795,9 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 
 #ifdef USE_INJECTION_POINTS
 		if (idx->safe)
-			INJECTION_POINT("reindex-conc-index-safe");
+			INJECTION_POINT("reindex-conc-index-safe", NULL);
 		else
-			INJECTION_POINT("reindex-conc-index-not-safe");
+			INJECTION_POINT("reindex-conc-index-not-safe", NULL);
 #endif
 
 		idx->tableId = RelationGetRelid(heapRel);
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a2132ecedaf8..132cad6c7a06 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2173,11 +2173,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
 
 #ifdef USE_INJECTION_POINTS
 	if (params->index_cleanup == VACOPTVALUE_AUTO)
-		INJECTION_POINT("vacuum-index-cleanup-auto");
+		INJECTION_POINT("vacuum-index-cleanup-auto", NULL);
 	else if (params->index_cleanup == VACOPTVALUE_DISABLED)
-		INJECTION_POINT("vacuum-index-cleanup-disabled");
+		INJECTION_POINT("vacuum-index-cleanup-disabled", NULL);
 	else if (params->index_cleanup == VACOPTVALUE_ENABLED)
-		INJECTION_POINT("vacuum-index-cleanup-enabled");
+		INJECTION_POINT("vacuum-index-cleanup-enabled", NULL);
 #endif
 
 	/*
@@ -2195,11 +2195,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params,
 
 #ifdef USE_INJECTION_POINTS
 	if (params->truncate == VACOPTVALUE_AUTO)
-		INJECTION_POINT("vacuum-truncate-auto");
+		INJECTION_POINT("vacuum-truncate-auto", NULL);
 	else if (params->truncate == VACOPTVALUE_DISABLED)
-		INJECTION_POINT("vacuum-truncate-disabled");
+		INJECTION_POINT("vacuum-truncate-disabled", NULL);
 	else if (params->truncate == VACOPTVALUE_ENABLED)
-		INJECTION_POINT("vacuum-truncate-enabled");
+		INJECTION_POINT("vacuum-truncate-enabled", NULL);
 #endif
 
 	/*
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 206fb932484c..d07fcd089b9d 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -1923,7 +1923,7 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn)
 
 			/* trigger injection point, but only if segment changes */
 			if (seg1 != seg2)
-				INJECTION_POINT("logical-replication-slot-advance-segment");
+				INJECTION_POINT("logical-replication-slot-advance-segment", NULL);
 #endif
 
 			ReplicationSlotMarkDirty();
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 9cd1d0abe35f..a4e56a02c0ea 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -3442,7 +3442,7 @@ ProcessInterrupts(void)
 		IdleInTransactionSessionTimeoutPending = false;
 		if (IdleInTransactionSessionTimeout > 0)
 		{
-			INJECTION_POINT("idle-in-transaction-session-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")));
@@ -3455,7 +3455,7 @@ ProcessInterrupts(void)
 		TransactionTimeoutPending = false;
 		if (TransactionTimeout > 0)
 		{
-			INJECTION_POINT("transaction-timeout");
+			INJECTION_POINT("transaction-timeout", NULL);
 			ereport(FATAL,
 					(errcode(ERRCODE_TRANSACTION_TIMEOUT),
 					 errmsg("terminating connection due to transaction timeout")));
@@ -3468,7 +3468,7 @@ ProcessInterrupts(void)
 		IdleSessionTimeoutPending = false;
 		if (IdleSessionTimeout > 0)
 		{
-			INJECTION_POINT("idle-session-timeout");
+			INJECTION_POINT("idle-session-timeout", NULL);
 			ereport(FATAL,
 					(errcode(ERRCODE_IDLE_SESSION_TIMEOUT),
 					 errmsg("terminating connection due to idle-session timeout")));
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 59d625b244c7..900da8b3bf1b 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -1906,7 +1906,7 @@ SearchCatCacheList(CatCache *cache,
 			/* Injection point to help testing the recursive invalidation case */
 			if (first_iter)
 			{
-				INJECTION_POINT("catcache-list-miss-systable-scan-started");
+				INJECTION_POINT("catcache-list-miss-systable-scan-started", NULL);
 				first_iter = false;
 			}
 
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 66e04f973f67..77473b8d9e7f 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -1032,7 +1032,7 @@ AtEOXact_Inval(bool isCommit)
 	/* Must be at top of stack */
 	Assert(transInvalInfo->my_level == 1 && transInvalInfo->parent == NULL);
 
-	INJECTION_POINT("transaction-end-process-inval");
+	INJECTION_POINT("transaction-end-process-inval", NULL);
 
 	if (isCommit)
 	{
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index b33cddefabc3..c80653a74589 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -17,6 +17,10 @@
  */
 #include "postgres.h"
 
+#include "utils/injection_point.h"
+
+#ifdef USE_INJECTION_POINTS
+
 #include <sys/stat.h>
 
 #include "fmgr.h"
@@ -25,11 +29,8 @@
 #include "storage/lwlock.h"
 #include "storage/shmem.h"
 #include "utils/hsearch.h"
-#include "utils/injection_point.h"
 #include "utils/memutils.h"
 
-#ifdef USE_INJECTION_POINTS
-
 /* Field sizes */
 #define INJ_NAME_MAXLEN		64
 #define INJ_LIB_MAXLEN		128
@@ -519,19 +520,67 @@ InjectionPointCacheRefresh(const char *name)
 }
 #endif
 
+/*
+ * Load an injection point into the local cache.
+ *
+ * This is useful to be able to load an injection point before running it,
+ * especially if the injection point is called in a code path where memory
+ * allocations cannot happen, like critical sections.
+ */
+void
+InjectionPointLoad(const char *name)
+{
+#ifdef USE_INJECTION_POINTS
+	InjectionPointCacheRefresh(name);
+#else
+	elog(ERROR, "Injection points are not supported by this build");
+#endif
+}
+
 /*
  * Execute an injection point, if defined.
  */
 void
-InjectionPointRun(const char *name)
+InjectionPointRun(const char *name, void *arg)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointCacheEntry *cache_entry;
 
 	cache_entry = InjectionPointCacheRefresh(name);
 	if (cache_entry)
-		cache_entry->callback(name, cache_entry->private_data);
+		cache_entry->callback(name, cache_entry->private_data, arg);
 #else
 	elog(ERROR, "Injection points are not supported by this build");
 #endif
 }
+
+/*
+ * Execute an injection point directly from the cache, if defined.
+ */
+void
+InjectionPointCached(const char *name, void *arg)
+{
+#ifdef USE_INJECTION_POINTS
+	InjectionPointCacheEntry *cache_entry;
+
+	cache_entry = injection_point_cache_get(name);
+	if (cache_entry)
+		cache_entry->callback(name, cache_entry->private_data, arg);
+#else
+	elog(ERROR, "Injection points are not supported by this build");
+#endif
+}
+
+/*
+ * Test if an injection point is defined.
+ */
+bool
+IsInjectionPointAttached(const char *name)
+{
+#ifdef USE_INJECTION_POINTS
+	return InjectionPointCacheRefresh(name) != NULL;
+#else
+	elog(ERROR, "Injection points are not supported by this build");
+	return false;				/* silence compiler */
+#endif
+}
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index dd9db06e10bd..43bcdd01582f 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -6,6 +6,19 @@ CREATE FUNCTION wait_pid(int)
   RETURNS void
   AS :'regresslib'
   LANGUAGE C STRICT;
+-- Non-strict checks
+SELECT injection_points_run(NULL);
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_cached(NULL);
+ injection_points_cached 
+-------------------------
+ 
+(1 row)
+
 SELECT injection_points_attach('TestInjectionBooh', 'booh');
 ERROR:  incorrect action "booh" for injection point creation
 SELECT injection_points_attach('TestInjectionError', 'error');
@@ -39,6 +52,20 @@ NOTICE:  notice triggered for injection point TestInjectionLog2
  
 (1 row)
 
+SELECT injection_points_run('TestInjectionLog2', NULL); -- notice
+NOTICE:  notice triggered for injection point TestInjectionLog2
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestInjectionLog2', 'foobar'); -- notice + arg
+NOTICE:  notice triggered for injection point TestInjectionLog2 (foobar)
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
 SELECT injection_points_run('TestInjectionLog'); -- notice
 NOTICE:  notice triggered for injection point TestInjectionLog
  injection_points_run 
@@ -48,6 +75,10 @@ NOTICE:  notice triggered for injection point TestInjectionLog
 
 SELECT injection_points_run('TestInjectionError'); -- error
 ERROR:  error triggered for injection point TestInjectionError
+SELECT injection_points_run('TestInjectionError', NULL); -- error
+ERROR:  error triggered for injection point TestInjectionError
+SELECT injection_points_run('TestInjectionError', 'foobar2'); -- error + arg
+ERROR:  error triggered for injection point TestInjectionError (foobar2)
 -- Re-load cache and run again.
 \c
 SELECT injection_points_run('TestInjectionLog2'); -- notice
@@ -128,6 +159,65 @@ SELECT injection_points_detach('TestInjectionLog2');
  
 (1 row)
 
+-- Loading
+SELECT injection_points_cached('TestInjectionLogLoad'); -- nothing in cache
+ injection_points_cached 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
+ injection_points_load 
+-----------------------
+ 
+(1 row)
+
+SELECT injection_points_attach('TestInjectionLogLoad', 'notice');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing happens
+ injection_points_load 
+-----------------------
+ 
+(1 row)
+
+SELECT injection_points_cached('TestInjectionLogLoad'); -- runs from cache
+NOTICE:  notice triggered for injection point TestInjectionLogLoad
+ injection_points_cached 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_cached('TestInjectionLogLoad', NULL); -- runs from cache
+NOTICE:  notice triggered for injection point TestInjectionLogLoad
+ injection_points_cached 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_cached('TestInjectionLogLoad', 'foobar'); -- runs from cache
+NOTICE:  notice triggered for injection point TestInjectionLogLoad (foobar)
+ injection_points_cached 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestInjectionLogLoad'); -- runs from cache
+NOTICE:  notice triggered for injection point TestInjectionLogLoad
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_detach('TestInjectionLogLoad');
+ injection_points_detach 
+-------------------------
+ 
+(1 row)
+
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
  injection_points_attach 
diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql
index 519641e6d041..a737683a9b2c 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -14,15 +14,37 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_load()
+--
+-- Load an injection point already attached.
+--
+CREATE FUNCTION injection_points_load(IN point_name TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_load'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_run()
 --
 -- Executes the action attached to the injection point.
 --
-CREATE FUNCTION injection_points_run(IN point_name TEXT)
+CREATE FUNCTION injection_points_run(IN point_name TEXT,
+    IN arg TEXT DEFAULT NULL)
 RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_run'
-LANGUAGE C STRICT PARALLEL UNSAFE;
+LANGUAGE C PARALLEL UNSAFE;
+
+--
+-- injection_points_cached()
+--
+-- Executes the action attached to the injection point, from local cache.
+--
+CREATE FUNCTION injection_points_cached(IN point_name TEXT,
+    IN arg TEXT DEFAULT NULL)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_cached'
+LANGUAGE C PARALLEL UNSAFE;
 
 --
 -- injection_points_wakeup()
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 1b695a182032..b2048e310e25 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -87,11 +87,14 @@ typedef struct InjectionPointSharedState
 static InjectionPointSharedState *inj_state = NULL;
 
 extern PGDLLEXPORT void injection_error(const char *name,
-										const void *private_data);
+										const void *private_data,
+										void *arg);
 extern PGDLLEXPORT void injection_notice(const char *name,
-										 const void *private_data);
+										 const void *private_data,
+										 void *arg);
 extern PGDLLEXPORT void injection_wait(const char *name,
-									   const void *private_data);
+									   const void *private_data,
+									   void *arg);
 
 /* track if injection points attached in this process are linked to it */
 static bool injection_point_local = false;
@@ -175,30 +178,40 @@ injection_points_cleanup(int code, Datum arg)
 
 /* Set of callbacks available to be attached to an injection point. */
 void
-injection_error(const char *name, const void *private_data)
+injection_error(const char *name, const void *private_data, void *arg)
 {
 	InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
+	char	   *argstr = (char *) arg;
 
 	if (!injection_point_allowed(condition))
 		return;
 
-	elog(ERROR, "error triggered for injection point %s", name);
+	if (argstr)
+		elog(ERROR, "error triggered for injection point %s (%s)",
+			 name, argstr);
+	else
+		elog(ERROR, "error triggered for injection point %s", name);
 }
 
 void
-injection_notice(const char *name, const void *private_data)
+injection_notice(const char *name, const void *private_data, void *arg)
 {
 	InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
+	char	   *argstr = (char *) arg;
 
 	if (!injection_point_allowed(condition))
 		return;
 
-	elog(NOTICE, "notice triggered for injection point %s", name);
+	if (argstr)
+		elog(NOTICE, "notice triggered for injection point %s (%s)",
+			 name, argstr);
+	else
+		elog(NOTICE, "notice triggered for injection point %s", name);
 }
 
 /* Wait on a condition variable, awaken by injection_points_wakeup() */
 void
-injection_wait(const char *name, const void *private_data)
+injection_wait(const char *name, const void *private_data, void *arg)
 {
 	uint32		old_wait_counts = 0;
 	int			index = -1;
@@ -299,6 +312,24 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
 		MemoryContextSwitchTo(oldctx);
 	}
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * SQL function for loading an injection point.
+ */
+PG_FUNCTION_INFO_V1(injection_points_load);
+Datum
+injection_points_load(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	if (inj_state == NULL)
+		injection_init_shmem();
+
+	INJECTION_POINT_LOAD(name);
+
 	PG_RETURN_VOID();
 }
 
@@ -309,9 +340,39 @@ PG_FUNCTION_INFO_V1(injection_points_run);
 Datum
 injection_points_run(PG_FUNCTION_ARGS)
 {
-	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *name;
+	char	   *arg = NULL;
 
-	INJECTION_POINT(name);
+	if (PG_ARGISNULL(0))
+		PG_RETURN_VOID();
+	name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	INJECTION_POINT(name, arg);
+
+	PG_RETURN_VOID();
+}
+
+/*
+ * SQL function for triggering an injection point from cache.
+ */
+PG_FUNCTION_INFO_V1(injection_points_cached);
+Datum
+injection_points_cached(PG_FUNCTION_ARGS)
+{
+	char	   *name;
+	char	   *arg = NULL;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_VOID();
+	name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	if (!PG_ARGISNULL(1))
+		arg = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	INJECTION_POINT_CACHED(name, arg);
 
 	PG_RETURN_VOID();
 }
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index 71e2972a7e49..d9748331c771 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -9,6 +9,10 @@ CREATE FUNCTION wait_pid(int)
   AS :'regresslib'
   LANGUAGE C STRICT;
 
+-- Non-strict checks
+SELECT injection_points_run(NULL);
+SELECT injection_points_cached(NULL);
+
 SELECT injection_points_attach('TestInjectionBooh', 'booh');
 SELECT injection_points_attach('TestInjectionError', 'error');
 SELECT injection_points_attach('TestInjectionLog', 'notice');
@@ -16,8 +20,12 @@ SELECT injection_points_attach('TestInjectionLog2', 'notice');
 
 SELECT injection_points_run('TestInjectionBooh'); -- nothing
 SELECT injection_points_run('TestInjectionLog2'); -- notice
+SELECT injection_points_run('TestInjectionLog2', NULL); -- notice
+SELECT injection_points_run('TestInjectionLog2', 'foobar'); -- notice + arg
 SELECT injection_points_run('TestInjectionLog'); -- notice
 SELECT injection_points_run('TestInjectionError'); -- error
+SELECT injection_points_run('TestInjectionError', NULL); -- error
+SELECT injection_points_run('TestInjectionError', 'foobar2'); -- error + arg
 
 -- Re-load cache and run again.
 \c
@@ -41,6 +49,17 @@ SELECT injection_points_detach('TestInjectionLog'); -- fails
 SELECT injection_points_run('TestInjectionLog2'); -- notice
 SELECT injection_points_detach('TestInjectionLog2');
 
+-- Loading
+SELECT injection_points_cached('TestInjectionLogLoad'); -- nothing in cache
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
+SELECT injection_points_attach('TestInjectionLogLoad', 'notice');
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing happens
+SELECT injection_points_cached('TestInjectionLogLoad'); -- runs from cache
+SELECT injection_points_cached('TestInjectionLogLoad', NULL); -- runs from cache
+SELECT injection_points_cached('TestInjectionLogLoad', 'foobar'); -- runs from cache
+SELECT injection_points_run('TestInjectionLogLoad'); -- runs from cache
+SELECT injection_points_detach('TestInjectionLogLoad');
+
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
 -- Any follow-up injection point attached will be local to this process.
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index a8aa871918a7..7f42b25ddbe9 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3608,15 +3608,34 @@ uint32 WaitEventExtensionNew(const char *wait_event_name)
      An injection point with a given <literal>name</literal> is declared using
      macro:
 <programlisting>
-INJECTION_POINT(name);
+INJECTION_POINT(name, arg);
 </programlisting>
 
      There are a few injection points already declared at strategic points
      within the server code. After adding a new injection point the code needs
      to be compiled in order for that injection point to be available in the
      binary. Add-ins written in C-language can declare injection points in
-     their own code using the same macro. The injection point names should
-     use lower-case characters, with terms separated by dashes.
+     their own code using the same macro. The injection point names should use
+     lower-case characters, with terms separated by
+     dashes. <literal>arg</literal> is an optional argument value given to the
+     callback at run-time.
+    </para>
+
+    <para>
+     Executing an injection point can require allocating a small amount of
+     memory, which can fail. If you need to have an injection point in a
+     critical section where dynamic allocations are not allowed, you can use
+     a two-step approach with the following macros:
+<programlisting>
+INJECTION_POINT_LOAD(name);
+INJECTION_POINT_CACHED(name, arg);
+</programlisting>
+
+     Before entering the critical section,
+     call <function>INJECTION_POINT_LOAD</function>. It checks the shared
+     memory state, and loads the callback into backend-private memory if it is
+     active. Inside the critical section, use
+     <function>INJECTION_POINT_CACHED</function> to execute the callback.
     </para>
 
     <para>
@@ -3642,7 +3661,9 @@ extern void InjectionPointAttach(const char *name,
      <literal>InjectionPointCallback</literal>:
 <programlisting>
 static void
-custom_injection_callback(const char *name, const void *private_data)
+custom_injection_callback(const char *name,
+                          const void *private_data,
+                          void *arg)
 {
     uint32 wait_event_info = WaitEventInjectionPointNew(name);
 
@@ -3656,6 +3677,31 @@ custom_injection_callback(const char *name, const void *private_data)
      logic.
     </para>
 
+    <para>
+     An alternative way to define the action to take when an injection point
+     is reached is to add the testing code alongside the normal source
+     code. This can be useful if the action e.g. depends on local variables
+     that are not accessible to loaded modules. The
+     <function>IS_INJECTION_POINT_ATTACHED</function> macro can then be used
+     to check if an injection point is attached, for example:
+<programlisting>
+#ifdef USE_INJECTION_POINTS
+if (IS_INJECTION_POINT_ATTACHED("before-foobar"))
+{
+    /* change a local variable if injection point is attached */
+    local_var = 123;
+
+    /* also execute the callback */
+    INJECTION_POINT_CACHED("before-foobar", NULL);
+}
+#endif
+</programlisting>
+     Note that the callback attached to the injection point will not be
+     executed by the <function>IS_INJECTION_POINT_ATTACHED</function>
+     macro. If you want to execute the callback, you must also call
+     <function>INJECTION_POINT_CACHED</function> like in the above example.
+    </para>
+
     <para>
      Optionally, it is possible to detach an injection point by calling:
 <programlisting>
-- 
2.50.0

