Hi all,

I have a couple of extra toys for injection points in my bucket that
I'd like to propose for integration in v18, based on some feedback I
have received:
1) Preload an injection point into the backend-level cache without
running it.  This has come up because an injection point run for the
first time needs to be loaded with load_external_function that
internally does some allocations, and this would not work if the
injection point is in a critical section.  Being able to preload an 
injection point requires an extra macro, called
INJECTION_POINT_PRELOAD.  Perhaps "load" makes more sense as keyword,
here.
2) Grab values at runtime from the code path where an injection point
is run and give them to the callback.  The case here is to be able to
do some dynamic manipulation or a stack, reads of some runtime data or
even decide of a different set of actions in a callback based on what
the input has provided.  One case that I've been playing with here is
the dynamic manipulation of pages in specific code paths to stress
internal checks, as one example.  This introduces a 1-argument
version, as multiple args could always be passed down to the callback
within a structure.

The in-core module injection_points is extended to provide a SQL
interface to be able to do the preloading or define a callback with
arguments.  The two changes are split into patches of their own.

These new facilities could be backpatched if there is a need for them
in the future in stable branches, as these are aimed for tests and the
changes do not introduce any ABI breakages with the existing APIs or
the in-core module.

Thoughts and comments are welcome.
--
Michael
From ed617725d1e6a156363384ed5fcbf4e79b5f8ab4 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Mon, 20 May 2024 11:50:42 +0900
Subject: [PATCH 1/2] Extend injection points with optional runtime arguments

This extends injections points with a 1-argument flavor, so as it
becomes possible for callbacks to pass down values coming from the code
paths where an injection point is defined.  The primary use case that
can be covered in this function is runtime manipulation of a given
stack, where it would be possible to corrupt a running state, based on a
runtime set of conditions.

For example, imagine a class of failures in the solar flare category
causing bits to flip on a page.
---
 src/include/utils/injection_point.h           |  9 +-
 src/backend/utils/misc/injection_point.c      | 92 +++++++++++++------
 .../expected/injection_points.out             | 31 +++++++
 .../injection_points--1.0.sql                 | 10 ++
 .../injection_points/injection_points.c       | 39 +++++++-
 .../injection_points/sql/injection_points.sql |  9 ++
 doc/src/sgml/xfunc.sgml                       |  9 +-
 7 files changed, 169 insertions(+), 30 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index a61d5d4439..c2c0840706 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -16,8 +16,10 @@
  */
 #ifdef USE_INJECTION_POINTS
 #define INJECTION_POINT(name) InjectionPointRun(name)
+#define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1)
 #else
 #define INJECTION_POINT(name) ((void) name)
+#define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1)
 #endif
 
 /*
@@ -25,6 +27,9 @@
  */
 typedef void (*InjectionPointCallback) (const char *name,
 										const void *private_data);
+typedef void (*InjectionPointCallback1Arg) (const char *name,
+											const void *private_data,
+											const void *arg1);
 
 extern Size InjectionPointShmemSize(void);
 extern void InjectionPointShmemInit(void);
@@ -33,8 +38,10 @@ extern void InjectionPointAttach(const char *name,
 								 const char *library,
 								 const char *function,
 								 const void *private_data,
-								 int private_data_size);
+								 int private_data_size,
+								 int num_args);
 extern void InjectionPointRun(const char *name);
+extern void InjectionPointRun1Arg(const char *name, void *arg1);
 extern bool InjectionPointDetach(const char *name);
 
 #endif							/* INJECTION_POINT_H */
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index 5c2a0d2297..2bcdb2708c 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -56,6 +56,9 @@ typedef struct InjectionPointEntry
 	 * callbacks, registered when attached.
 	 */
 	char		private_data[INJ_PRIVATE_MAXLEN];
+
+	/* Number of arguments used by the callback */
+	int			num_args;
 } InjectionPointEntry;
 
 #define INJECTION_POINT_HASH_INIT_SIZE	16
@@ -69,7 +72,12 @@ typedef struct InjectionPointCacheEntry
 {
 	char		name[INJ_NAME_MAXLEN];
 	char		private_data[INJ_PRIVATE_MAXLEN];
-	InjectionPointCallback callback;
+	int			num_args;
+	union
+	{
+		InjectionPointCallback callback;
+		InjectionPointCallback1Arg callback_1arg;
+	} data;
 } InjectionPointCacheEntry;
 
 static HTAB *InjectionPointCache = NULL;
@@ -81,8 +89,9 @@ static HTAB *InjectionPointCache = NULL;
  */
 static void
 injection_point_cache_add(const char *name,
-						  InjectionPointCallback callback,
-						  const void *private_data)
+						  void *callback,
+						  const void *private_data,
+						  int num_args)
 {
 	InjectionPointCacheEntry *entry;
 	bool		found;
@@ -107,7 +116,14 @@ injection_point_cache_add(const char *name,
 
 	Assert(!found);
 	strlcpy(entry->name, name, sizeof(entry->name));
-	entry->callback = callback;
+	entry->num_args = num_args;
+	if (num_args == 0)
+		entry->data.callback = (InjectionPointCallback) callback;
+	else if (num_args == 1)
+		entry->data.callback_1arg = (InjectionPointCallback1Arg) callback;
+	else
+		elog(ERROR, "unsupported number of arguments for injection point \"%s\"",
+			 name);
 	if (private_data != NULL)
 		memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
 }
@@ -134,15 +150,12 @@ injection_point_cache_remove(const char *name)
  *
  * Retrieve an injection point from the local cache, if any.
  */
-static InjectionPointCallback
-injection_point_cache_get(const char *name, const void **private_data)
+static InjectionPointCacheEntry *
+injection_point_cache_get(const char *name)
 {
 	bool		found;
 	InjectionPointCacheEntry *entry;
 
-	if (private_data)
-		*private_data = NULL;
-
 	/* no callback if no cache yet */
 	if (InjectionPointCache == NULL)
 		return NULL;
@@ -151,11 +164,7 @@ injection_point_cache_get(const char *name, const void **private_data)
 		hash_search(InjectionPointCache, name, HASH_FIND, &found);
 
 	if (found)
-	{
-		if (private_data)
-			*private_data = entry->private_data;
-		return entry->callback;
-	}
+		return entry;
 
 	return NULL;
 }
@@ -206,7 +215,8 @@ InjectionPointAttach(const char *name,
 					 const char *library,
 					 const char *function,
 					 const void *private_data,
-					 int private_data_size)
+					 int private_data_size,
+					 int num_args)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointEntry *entry_by_name;
@@ -248,6 +258,7 @@ InjectionPointAttach(const char *name,
 	entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
 	if (private_data != NULL)
 		memcpy(entry_by_name->private_data, private_data, private_data_size);
+	entry_by_name->num_args = num_args;
 
 	LWLockRelease(InjectionPointLock);
 
@@ -282,19 +293,18 @@ InjectionPointDetach(const char *name)
 }
 
 /*
- * Execute an injection point, if defined.
+ * Workhorse for execution of an injection point, if defined.
  *
  * Check first the shared hash table, and adapt the local cache depending
  * on that as it could be possible that an entry to run has been removed.
  */
-void
-InjectionPointRun(const char *name)
+static inline void
+InjectionPointRunInternal(const char *name, int num_args, void *arg1)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointEntry *entry_by_name;
 	bool		found;
-	InjectionPointCallback injection_callback;
-	const void *private_data;
+	InjectionPointCacheEntry *cache_entry;
 
 	LWLockAcquire(InjectionPointLock, LW_SHARED);
 	entry_by_name = (InjectionPointEntry *)
@@ -316,10 +326,10 @@ InjectionPointRun(const char *name)
 	 * Check if the callback exists in the local cache, to avoid unnecessary
 	 * external loads.
 	 */
-	if (injection_point_cache_get(name, NULL) == NULL)
+	if (injection_point_cache_get(name) == NULL)
 	{
 		char		path[MAXPGPATH];
-		InjectionPointCallback injection_callback_local;
+		void	   *injection_callback_local;
 
 		/* not found in local cache, so load and register */
 		snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
@@ -329,7 +339,7 @@ InjectionPointRun(const char *name)
 			elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
 				 path, name);
 
-		injection_callback_local = (InjectionPointCallback)
+		injection_callback_local = (void *)
 			load_external_function(path, entry_by_name->function, false, NULL);
 
 		if (injection_callback_local == NULL)
@@ -338,13 +348,43 @@ InjectionPointRun(const char *name)
 
 		/* add it to the local cache when found */
 		injection_point_cache_add(name, injection_callback_local,
-								  entry_by_name->private_data);
+								  entry_by_name->private_data,
+								  entry_by_name->num_args);
 	}
 
 	/* Now loaded, so get it. */
-	injection_callback = injection_point_cache_get(name, &private_data);
-	injection_callback(name, private_data);
+	cache_entry = injection_point_cache_get(name);
+
+	/* Check the number of arguments with the cache. */
+	if (cache_entry->num_args != num_args)
+		elog(ERROR, "incorrect number of arguments for injection point \"%s\": defined %d but expected %d",
+			 name, cache_entry->num_args, num_args);
+
+	if (cache_entry->num_args == 0)
+		cache_entry->data.callback(name, cache_entry->private_data);
+	else if (cache_entry->num_args == 1)
+		cache_entry->data.callback_1arg(name, cache_entry->private_data, arg1);
+	else
+	{
+		/* cannot be reached */
+		Assert(false);
+	}
 #else
 	elog(ERROR, "Injection points are not supported by this build");
 #endif
 }
+
+/*
+ * Execute an injection point, with no arguments.
+ */
+void
+InjectionPointRun(const char *name)
+{
+	InjectionPointRunInternal(name, 0, NULL);
+}
+/* 1-argument version */
+void
+InjectionPointRun1Arg(const char *name, void *arg1)
+{
+	InjectionPointRunInternal(name, 1, arg1);
+}
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index dd9db06e10..740bdc8cfd 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -122,12 +122,43 @@ NOTICE:  notice triggered for injection point TestInjectionLog2
  
 (1 row)
 
+-- 1-argument runs
+SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error
+ERROR:  incorrect number of arguments for injection point "TestInjectionLog2": defined 0 but expected 1
+SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestInjectionLogArg1'); -- error
+ERROR:  incorrect number of arguments for injection point "TestInjectionLogArg1": defined 1 but expected 0
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice
+NOTICE:  notice triggered for injection point TestInjectionLogArg1: blah
+ injection_points_run_1arg 
+---------------------------
+ 
+(1 row)
+
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice
+NOTICE:  notice triggered for injection point TestInjectionLogArg1: foobar
+ injection_points_run_1arg 
+---------------------------
+ 
+(1 row)
+
 SELECT injection_points_detach('TestInjectionLog2');
  injection_points_detach 
 -------------------------
  
 (1 row)
 
+SELECT injection_points_detach('TestInjectionLogArg1');
+ 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 c16a33b08d..074b7b2ea7 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -45,6 +45,16 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_set_local'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_run_1arg()
+--
+-- Executes the action attached to the injection point.
+--
+CREATE FUNCTION injection_points_run_1arg(IN point_name TEXT, IN arg1 TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_run_1arg'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_detach()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 5c44625d1d..7ef1c471a0 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -90,6 +90,9 @@ extern PGDLLEXPORT void injection_error(const char *name,
 										const void *private_data);
 extern PGDLLEXPORT void injection_notice(const char *name,
 										 const void *private_data);
+extern PGDLLEXPORT void injection_notice_1arg(const char *name,
+											  const void *private_data,
+											  const void *arg1);
 extern PGDLLEXPORT void injection_wait(const char *name,
 									   const void *private_data);
 
@@ -260,6 +263,19 @@ injection_wait(const char *name, const void *private_data)
 	SpinLockRelease(&inj_state->lock);
 }
 
+void
+injection_notice_1arg(const char *name, const void *private_data,
+					  const void *arg1)
+{
+	InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
+	const char *str = (const char *) arg1;
+
+	if (!injection_point_allowed(condition))
+		return;
+
+	elog(NOTICE, "notice triggered for injection point %s: %s", name, str);
+}
+
 /*
  * SQL function for creating an injection point.
  */
@@ -271,6 +287,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	char	   *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
 	char	   *function;
 	InjectionPointCondition condition = {0};
+	int			num_args = 0;
 
 	if (strcmp(action, "error") == 0)
 		function = "injection_error";
@@ -278,6 +295,11 @@ injection_points_attach(PG_FUNCTION_ARGS)
 		function = "injection_notice";
 	else if (strcmp(action, "wait") == 0)
 		function = "injection_wait";
+	else if (strcmp(action, "notice_1arg") == 0)
+	{
+		function = "injection_notice_1arg";
+		num_args = 1;
+	}
 	else
 		elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
 
@@ -288,7 +310,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	}
 
 	InjectionPointAttach(name, "injection_points", function, &condition,
-						 sizeof(InjectionPointCondition));
+						 sizeof(InjectionPointCondition), num_args);
 
 	if (injection_point_local)
 	{
@@ -378,6 +400,21 @@ injection_points_set_local(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for triggering an injection point, 1-argument flavor.
+ */
+PG_FUNCTION_INFO_V1(injection_points_run_1arg);
+Datum
+injection_points_run_1arg(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	   *arg1 = text_to_cstring(PG_GETARG_TEXT_PP(1));
+
+	INJECTION_POINT_1ARG(name, (void *) arg1);
+
+	PG_RETURN_VOID();
+}
+
 /*
  * SQL function for dropping an injection point.
  */
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index 71e2972a7e..5b612a75c3 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -39,7 +39,16 @@ SELECT injection_points_run('TestInjectionLog2'); -- notice
 SELECT injection_points_detach('TestInjectionLog'); -- fails
 
 SELECT injection_points_run('TestInjectionLog2'); -- notice
+
+-- 1-argument runs
+SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error
+SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg');
+SELECT injection_points_run('TestInjectionLogArg1'); -- error
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice
+SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice
+
 SELECT injection_points_detach('TestInjectionLog2');
+SELECT injection_points_detach('TestInjectionLogArg1');
 
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index a7c170476a..b74a2c1040 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3609,13 +3609,15 @@ uint32 WaitEventExtensionNew(const char *wait_event_name)
      macro:
 <programlisting>
 INJECTION_POINT(name);
+INJECTION_POINT_1ARG(name, arg1);
 </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.
+     their own code using the same macro. It is possible to pass down arguments
+     to callbacks for runtime checks.
     </para>
 
     <para>
@@ -3626,7 +3628,8 @@ extern void InjectionPointAttach(const char *name,
                                  const char *library,
                                  const char *function,
                                  const void *private_data,
-                                 int private_data_size);
+                                 int private_data_size,
+                                 int num_args);
 </programlisting>
 
      <literal>name</literal> is the name of the injection point, which when
@@ -3634,6 +3637,8 @@ extern void InjectionPointAttach(const char *name,
      loaded from <literal>library</literal>. <literal>private_data</literal>
      is a private area of data of size <literal>private_data_size</literal>
      given as argument to the callback when executed.
+     <literal>num_args</literal> is the number of arguments used by the
+     callback.
     </para>
 
     <para>
-- 
2.43.0

From 4dd79183e070069287c403c18a72768acf88f316 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Mon, 20 May 2024 11:48:06 +0900
Subject: [PATCH 2/2] Support preloading of injection points

This can be used to load an injection point in the backend-level cache
before running it, to avoid issues if the point cannot be loaded due to
restrictions in the code path where it would be run, like a critical
section.
---
 src/include/utils/injection_point.h           |  3 +
 src/backend/utils/misc/injection_point.c      | 96 ++++++++++++++-----
 .../expected/injection_points.out             | 32 +++++++
 .../injection_points--1.0.sql                 | 10 ++
 .../injection_points/injection_points.c       | 14 +++
 .../injection_points/sql/injection_points.sql |  7 ++
 6 files changed, 140 insertions(+), 22 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index c2c0840706..9ef411cf29 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -15,9 +15,11 @@
  * Injections points require --enable-injection-points.
  */
 #ifdef USE_INJECTION_POINTS
+#define INJECTION_POINT_PRELOAD(name) InjectionPointPreload(name)
 #define INJECTION_POINT(name) InjectionPointRun(name)
 #define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1)
 #else
+#define INJECTION_POINT_PRELOAD(name) ((void) name)
 #define INJECTION_POINT(name) ((void) name)
 #define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1)
 #endif
@@ -40,6 +42,7 @@ extern void InjectionPointAttach(const char *name,
 								 const void *private_data,
 								 int private_data_size,
 								 int num_args);
+extern void InjectionPointPreload(const char *name);
 extern void InjectionPointRun(const char *name);
 extern void InjectionPointRun1Arg(const char *name, void *arg1);
 extern bool InjectionPointDetach(const char *name);
diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c
index 2bcdb2708c..e4d317a286 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -145,6 +145,37 @@ injection_point_cache_remove(const char *name)
 	(void) hash_search(InjectionPointCache, name, HASH_REMOVE, NULL);
 }
 
+/*
+ * injection_point_cache_load
+ *
+ * Load an injection point into the local cache.
+ */
+static void
+injection_point_cache_load(InjectionPointEntry *entry_by_name)
+{
+	char		path[MAXPGPATH];
+	void	   *injection_callback_local;
+
+	snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
+			 entry_by_name->library, DLSUFFIX);
+
+	if (!pg_file_exists(path))
+		elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
+			 path, entry_by_name->name);
+
+	injection_callback_local = (void *)
+		load_external_function(path, entry_by_name->function, false, NULL);
+
+	if (injection_callback_local == NULL)
+		elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
+			 entry_by_name->function, path, entry_by_name->name);
+
+	/* add it to the local cache when found */
+	injection_point_cache_add(entry_by_name->name, injection_callback_local,
+							  entry_by_name->private_data,
+							  entry_by_name->num_args);
+}
+
 /*
  * injection_point_cache_get
  *
@@ -292,6 +323,47 @@ InjectionPointDetach(const char *name)
 #endif
 }
 
+/*
+ * Preload an injection point into the local cache.
+ *
+ * This is useful to be able to load a function pointer at a stage earlier
+ * than it would be run, especially if the injection point is called in a code
+ * path where memory allocations cannot happen, like critical sections.
+ */
+void
+InjectionPointPreload(const char *name)
+{
+#ifdef USE_INJECTION_POINTS
+	InjectionPointEntry *entry_by_name;
+	bool		found;
+
+	LWLockAcquire(InjectionPointLock, LW_SHARED);
+	entry_by_name = (InjectionPointEntry *)
+		hash_search(InjectionPointHash, name,
+					HASH_FIND, &found);
+	LWLockRelease(InjectionPointLock);
+
+	/*
+	 * If not found, do nothing and remove it from the local cache if it
+	 * existed there.
+	 */
+	if (!found)
+	{
+		injection_point_cache_remove(name);
+		return;
+	}
+
+	/* Check first the local cache, and leave if this entry exists. */
+	if (injection_point_cache_get(name) != NULL)
+		return;
+
+	/* Nothing?  Then load it and leave */
+	injection_point_cache_load(entry_by_name);
+#else
+	elog(ERROR, "Injection points are not supported by this build");
+#endif
+}
+
 /*
  * Workhorse for execution of an injection point, if defined.
  *
@@ -328,28 +400,8 @@ InjectionPointRunInternal(const char *name, int num_args, void *arg1)
 	 */
 	if (injection_point_cache_get(name) == NULL)
 	{
-		char		path[MAXPGPATH];
-		void	   *injection_callback_local;
-
-		/* not found in local cache, so load and register */
-		snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
-				 entry_by_name->library, DLSUFFIX);
-
-		if (!pg_file_exists(path))
-			elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
-				 path, name);
-
-		injection_callback_local = (void *)
-			load_external_function(path, entry_by_name->function, false, NULL);
-
-		if (injection_callback_local == NULL)
-			elog(ERROR, "could not find function \"%s\" in library \"%s\" for injection point \"%s\"",
-				 entry_by_name->function, path, name);
-
-		/* add it to the local cache when found */
-		injection_point_cache_add(name, injection_callback_local,
-								  entry_by_name->private_data,
-								  entry_by_name->num_args);
+		/* not found in local cache, so load and register it */
+		injection_point_cache_load(entry_by_name);
 	}
 
 	/* Now loaded, so get it. */
diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out
index 740bdc8cfd..392dd85234 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -159,6 +159,38 @@ SELECT injection_points_detach('TestInjectionLogArg1');
  
 (1 row)
 
+-- Preloading
+SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing
+ injection_points_preload 
+--------------------------
+ 
+(1 row)
+
+SELECT injection_points_attach('TestInjectionLogPreload', 'notice');
+ injection_points_attach 
+-------------------------
+ 
+(1 row)
+
+SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing happens
+ injection_points_preload 
+--------------------------
+ 
+(1 row)
+
+SELECT injection_points_run('TestInjectionLogPreload'); -- runs from cache
+NOTICE:  notice triggered for injection point TestInjectionLogPreload
+ injection_points_run 
+----------------------
+ 
+(1 row)
+
+SELECT injection_points_detach('TestInjectionLogPreload');
+ 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 074b7b2ea7..29d43cc556 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -14,6 +14,16 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_attach'
 LANGUAGE C STRICT PARALLEL UNSAFE;
 
+--
+-- injection_points_preload()
+--
+-- Preload an injection point already attached.
+--
+CREATE FUNCTION injection_points_preload(IN point_name TEXT)
+RETURNS void
+AS 'MODULE_PATHNAME', 'injection_points_preload'
+LANGUAGE C STRICT PARALLEL UNSAFE;
+
 --
 -- injection_points_run()
 --
diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c
index 7ef1c471a0..086b8a6a5e 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -324,6 +324,20 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	PG_RETURN_VOID();
 }
 
+/*
+ * SQL function for preloading an injection point.
+ */
+PG_FUNCTION_INFO_V1(injection_points_preload);
+Datum
+injection_points_preload(PG_FUNCTION_ARGS)
+{
+	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+	INJECTION_POINT_PRELOAD(name);
+
+	PG_RETURN_VOID();
+}
+
 /*
  * SQL function for triggering an injection point.
  */
diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql
index 5b612a75c3..69822be647 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -50,6 +50,13 @@ SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice
 SELECT injection_points_detach('TestInjectionLog2');
 SELECT injection_points_detach('TestInjectionLogArg1');
 
+-- Preloading
+SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing
+SELECT injection_points_attach('TestInjectionLogPreload', 'notice');
+SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing happens
+SELECT injection_points_run('TestInjectionLogPreload'); -- runs from cache
+SELECT injection_points_detach('TestInjectionLogPreload');
+
 -- Runtime conditions
 SELECT injection_points_attach('TestConditionError', 'error');
 -- Any follow-up injection point attached will be local to this process.
-- 
2.43.0

Attachment: signature.asc
Description: PGP signature

Reply via email to