On Tue, Jan 23, 2024 at 12:08:17PM +0900, Michael Paquier wrote:
> That was on my TODO list of things to tackle and propose, but perhaps
> there is no point in waiting more so I've applied your patch.

Slightly off topic and while I don't forget about it..  Please find
attached a copy of the patch posted around [1] to be able to define
injection points with input arguments, so as it is possible to execute
callbacks with values coming from the code path where the point is
attached.

For example, a backend could use this kind of macro to have a callback
attached to this point use some runtime value:
INJECTION_POINT_1ARG("InjectionPointBoo", &some_value);

[1]: https://www.postgresql.org/message-id/Za8TLyD9HIjzFlhJ%40paquier.xyz
--
Michael
From 1ebcd3c50c9db555b3d2078db931e8c8bdb87472 Mon Sep 17 00:00:00 2001
From: Michael Paquier <mich...@paquier.xyz>
Date: Tue, 23 Jan 2024 12:30:52 +0900
Subject: [PATCH] Extend injection points with optional runtime arguments

This extends injections points with a 1-argument flavor, so as it
becomes possible for callbacks to perform runtime checks.
---
 src/include/utils/injection_point.h           |  7 +-
 src/backend/utils/misc/injection_point.c      | 75 +++++++++++++++----
 .../expected/injection_points.out             | 25 +++++++
 .../injection_points--1.0.sql                 | 10 +++
 .../injection_points/injection_points.c       | 31 +++++++-
 .../injection_points/sql/injection_points.sql |  7 ++
 doc/src/sgml/xfunc.sgml                       | 10 ++-
 7 files changed, 146 insertions(+), 19 deletions(-)

diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h
index 55524b568f..8e1527bb4a 100644
--- a/src/include/utils/injection_point.h
+++ b/src/include/utils/injection_point.h
@@ -16,22 +16,27 @@
  */
 #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
 
 /*
  * Typedef for callback function launched by an injection point.
  */
 typedef void (*InjectionPointCallback) (const char *name);
+typedef void (*InjectionPointCallback1Arg) (const char *name, void *arg1);
 
 extern Size InjectionPointShmemSize(void);
 extern void InjectionPointShmemInit(void);
 
 extern void InjectionPointAttach(const char *name,
 								 const char *library,
-								 const char *function);
+								 const char *function,
+								 int num_args);
 extern void InjectionPointRun(const char *name);
+extern void InjectionPointRun1Arg(const char *name, void *arg1);
 extern void 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 0cf4d51cac..468085e963 100644
--- a/src/backend/utils/misc/injection_point.c
+++ b/src/backend/utils/misc/injection_point.c
@@ -49,6 +49,7 @@ typedef struct InjectionPointEntry
 	char		name[INJ_NAME_MAXLEN];	/* hash key */
 	char		library[INJ_LIB_MAXLEN];	/* library */
 	char		function[INJ_FUNC_MAXLEN];	/* function */
+	int			num_args;	/* number of arguments */
 } InjectionPointEntry;
 
 #define INJECTION_POINT_HASH_INIT_SIZE	16
@@ -61,7 +62,12 @@ typedef struct InjectionPointEntry
 typedef struct InjectionPointCacheEntry
 {
 	char		name[INJ_NAME_MAXLEN];
-	InjectionPointCallback callback;
+	int			num_args;
+	union
+	{
+		InjectionPointCallback callback;
+		InjectionPointCallback1Arg callback_1arg;
+	};
 } InjectionPointCacheEntry;
 
 static HTAB *InjectionPointCache = NULL;
@@ -73,7 +79,8 @@ static HTAB *InjectionPointCache = NULL;
  */
 static void
 injection_point_cache_add(const char *name,
-						  InjectionPointCallback callback)
+						  void *callback,
+						  int num_args)
 {
 	InjectionPointCacheEntry *entry;
 	bool		found;
@@ -99,6 +106,13 @@ 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->callback = (InjectionPointCallback) callback;
+	else if (num_args == 1)
+		entry->callback_1arg = (InjectionPointCallback1Arg) callback;
+	else
+		elog(ERROR, "unsupported number of arguments");	/* not reachable */
 }
 
 /*
@@ -123,7 +137,7 @@ injection_point_cache_remove(const char *name)
  *
  * Retrieve an injection point from the local cache, if any.
  */
-static InjectionPointCallback
+static InjectionPointCacheEntry *
 injection_point_cache_get(const char *name)
 {
 	bool		found;
@@ -137,7 +151,7 @@ injection_point_cache_get(const char *name)
 		hash_search(InjectionPointCache, name, HASH_FIND, &found);
 
 	if (found)
-		return entry->callback;
+		return entry;
 
 	return NULL;
 }
@@ -186,7 +200,8 @@ InjectionPointShmemInit(void)
 void
 InjectionPointAttach(const char *name,
 					 const char *library,
-					 const char *function)
+					 const char *function,
+					 int num_args)
 {
 #ifdef USE_INJECTION_POINTS
 	InjectionPointEntry *entry_by_name;
@@ -223,6 +238,7 @@ InjectionPointAttach(const char *name,
 	entry_by_name->library[INJ_LIB_MAXLEN - 1] = '\0';
 	strlcpy(entry_by_name->function, function, sizeof(entry_by_name->function));
 	entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
+	entry_by_name->num_args = num_args;
 
 	LWLockRelease(InjectionPointLock);
 
@@ -253,18 +269,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;
+	InjectionPointCacheEntry *cache_entry;
 
 	LWLockAcquire(InjectionPointLock, LW_SHARED);
 	entry_by_name = (InjectionPointEntry *)
@@ -282,14 +298,20 @@ InjectionPointRun(const char *name)
 		return;
 	}
 
+	if (entry_by_name->num_args != num_args)
+		elog(ERROR, "incorrect number of arguments in function \"%s\" for injection point \"%s\": defined %d but expected %d",
+			 entry_by_name->function, name,
+			 entry_by_name->num_args, num_args);
+
 	/*
 	 * Check if the callback exists in the local cache, to avoid unnecessary
 	 * external loads.
 	 */
-	injection_callback = injection_point_cache_get(name);
-	if (injection_callback == NULL)
+	cache_entry = injection_point_cache_get(name);
+	if (cache_entry == NULL)
 	{
 		char		path[MAXPGPATH];
+		void	   *injection_callback;
 
 		/* not found in local cache, so load and register */
 		snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path,
@@ -299,7 +321,7 @@ InjectionPointRun(const char *name)
 			elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
 				 path, name);
 
-		injection_callback = (InjectionPointCallback)
+		injection_callback = (void *)
 			load_external_function(path, entry_by_name->function, false, NULL);
 
 		if (injection_callback == NULL)
@@ -307,11 +329,36 @@ InjectionPointRun(const char *name)
 				 entry_by_name->function, path, name);
 
 		/* add it to the local cache when found */
-		injection_point_cache_add(name, injection_callback);
+		injection_point_cache_add(name, injection_callback, num_args);
+
+		/* and fetch it */
+		cache_entry = injection_point_cache_get(name);
 	}
 
-	injection_callback(name);
+	if (cache_entry->num_args == 0)
+		cache_entry->callback(name);
+	else if (cache_entry->num_args == 1)
+		cache_entry->callback_1arg(name, arg1);
+	else
+	{
+		Assert(false);	/* cannot be reached */
+	}
 #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 827456ccc5..7f50ba726d 100644
--- a/src/test/modules/injection_points/expected/injection_points.out
+++ b/src/test/modules/injection_points/expected/injection_points.out
@@ -115,4 +115,29 @@ 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 in function "injection_notice" 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 in function "injection_notice_1arg" 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)
+
 DROP EXTENSION injection_points;
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 5944c41716..9b99c17199 100644
--- a/src/test/modules/injection_points/injection_points--1.0.sql
+++ b/src/test/modules/injection_points/injection_points--1.0.sql
@@ -24,6 +24,16 @@ RETURNS void
 AS 'MODULE_PATHNAME', 'injection_points_run'
 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 e843e6594f..862f904a75 100644
--- a/src/test/modules/injection_points/injection_points.c
+++ b/src/test/modules/injection_points/injection_points.c
@@ -28,6 +28,7 @@ PG_MODULE_MAGIC;
 
 extern PGDLLEXPORT void injection_error(const char *name);
 extern PGDLLEXPORT void injection_notice(const char *name);
+extern PGDLLEXPORT void injection_notice_1arg(const char *name, void *arg1);
 
 
 /* Set of callbacks available to be attached to an injection point. */
@@ -43,6 +44,13 @@ injection_notice(const char *name)
 	elog(NOTICE, "notice triggered for injection point %s", name);
 }
 
+void
+injection_notice_1arg(const char *name, void *arg1)
+{
+	const char *str = (const char *) arg1;
+	elog(NOTICE, "notice triggered for injection point %s: %s", name, str);
+}
+
 /*
  * SQL function for creating an injection point.
  */
@@ -53,15 +61,21 @@ injection_points_attach(PG_FUNCTION_ARGS)
 	char	   *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 	char	   *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
 	char	   *function;
+	int			num_args = 0;
 
 	if (strcmp(action, "error") == 0)
 		function = "injection_error";
 	else if (strcmp(action, "notice") == 0)
 		function = "injection_notice";
+	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);
 
-	InjectionPointAttach(name, "injection_points", function);
+	InjectionPointAttach(name, "injection_points", function, num_args);
 
 	PG_RETURN_VOID();
 }
@@ -80,6 +94,21 @@ injection_points_run(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 23c7e435ad..eca528508b 100644
--- a/src/test/modules/injection_points/sql/injection_points.sql
+++ b/src/test/modules/injection_points/sql/injection_points.sql
@@ -31,4 +31,11 @@ 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
+
 DROP EXTENSION injection_points;
diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 8a79ad0943..7659c81c09 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -3607,13 +3607,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>
@@ -3622,12 +3624,14 @@ INJECTION_POINT(name);
 <programlisting>
 extern void InjectionPointAttach(const char *name,
                                  const char *library,
-                                 const char *function);
+                                 const char *function,
+                                 int num_args);
 </programlisting>
 
      <literal>name</literal> is the name of the injection point, which when
      reached during execution will execute the <literal>function</literal>
-     loaded from <literal>library</literal>.
+     loaded from <literal>library</literal>. <literal>num_args</literal> is
+     the number of arguments that are used by the callback.
     </para>
 
     <para>
-- 
2.43.0

Attachment: signature.asc
Description: PGP signature

Reply via email to