Support loading of injection points
authorMichael Paquier <[email protected]>
Fri, 5 Jul 2024 08:41:49 +0000 (17:41 +0900)
committerMichael Paquier <[email protected]>
Fri, 5 Jul 2024 09:09:03 +0000 (18:09 +0900)
This can be used to load an injection point and prewarm 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 where no memory allocation can happen
(load_external_function() can do allocations when expanding a library
name).

Tests can use a macro called INJECTION_POINT_LOAD() to load an injection
point.  The test module injection_points gains some tests, and a SQL
function able to load an injection point.

Based on a request from Andrey Borodin, who has implemented a test for
multixacts requiring this facility.

Reviewed-by: Andrey Borodin
Discussion: https://postgr.es/m/[email protected]

doc/src/sgml/xfunc.sgml
src/backend/utils/misc/injection_point.c
src/include/utils/injection_point.h
src/test/modules/injection_points/expected/injection_points.out
src/test/modules/injection_points/injection_points--1.0.sql
src/test/modules/injection_points/injection_points.c
src/test/modules/injection_points/sql/injection_points.sql

index f3a3e4e2f8f603010989a501e84bfb2d3655ca31..756a9d07fb00998dcdad776c4240ac1d5a73af28 100644 (file)
@@ -3618,6 +3618,20 @@ INJECTION_POINT(name);
      their own code using the same macro.
     </para>
 
+    <para>
+     An injection point with a given <literal>name</literal> can be loaded
+     using macro:
+<programlisting>
+INJECTION_POINT_LOAD(name);
+</programlisting>
+
+     This will load the injection point callback into the process cache,
+     doing all memory allocations at this stage without running the callback.
+     This is useful when an injection point is attached in a critical section
+     where no memory can be allocated: load the injection point outside the
+     critical section, then run it in the critical section.
+    </para>
+
     <para>
      Add-ins can attach callbacks to an already-declared injection point by
      calling:
index afae0dbedf416ce9f09f37f5925ff738245274d4..48f29e9b60ab79390187921262abec6245c16ff3 100644 (file)
@@ -129,20 +129,47 @@ 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);
+}
+
 /*
  * injection_point_cache_get
  *
  * 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 +178,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;
 }
@@ -278,6 +301,52 @@ InjectionPointDetach(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
+   InjectionPointEntry *entry_by_name;
+   bool        found;
+
+   LWLockAcquire(InjectionPointLock, LW_SHARED);
+   entry_by_name = (InjectionPointEntry *)
+       hash_search(InjectionPointHash, name,
+                   HASH_FIND, &found);
+
+   /*
+    * If not found, do nothing and remove it from the local cache if it
+    * existed there.
+    */
+   if (!found)
+   {
+       injection_point_cache_remove(name);
+       LWLockRelease(InjectionPointLock);
+       return;
+   }
+
+   /* Check first the local cache, and leave if this entry exists. */
+   if (injection_point_cache_get(name) != NULL)
+   {
+       LWLockRelease(InjectionPointLock);
+       return;
+   }
+
+   /* Nothing?  Then load it and leave */
+   injection_point_cache_load(entry_by_name);
+
+   LWLockRelease(InjectionPointLock);
+#else
+   elog(ERROR, "Injection points are not supported by this build");
+#endif
+}
+
 /*
  * Execute an injection point, if defined.
  *
@@ -290,8 +359,7 @@ InjectionPointRun(const char *name)
 #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 *)
@@ -313,37 +381,18 @@ 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;
-
-       /* 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 = (InjectionPointCallback)
-           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);
+       /* not found in local cache, so load and register it */
+       injection_point_cache_load(entry_by_name);
    }
 
    /* Now loaded, so get it. */
-   injection_callback = injection_point_cache_get(name, &private_data);
+   cache_entry = injection_point_cache_get(name);
 
    LWLockRelease(InjectionPointLock);
 
-   injection_callback(name, private_data);
+   cache_entry->callback(name, cache_entry->private_data);
 #else
    elog(ERROR, "Injection points are not supported by this build");
 #endif
index a61d5d44391d75056bb43870031f0eb8d8c21a40..bd3a62425c3e7b294738426aa6c9239d9b4cbfc1 100644 (file)
  * Injections points require --enable-injection-points.
  */
 #ifdef USE_INJECTION_POINTS
+#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
 #define INJECTION_POINT(name) InjectionPointRun(name)
 #else
+#define INJECTION_POINT_LOAD(name) ((void) name)
 #define INJECTION_POINT(name) ((void) name)
 #endif
 
@@ -34,6 +36,7 @@ extern void InjectionPointAttach(const char *name,
                                 const char *function,
                                 const void *private_data,
                                 int private_data_size);
+extern void InjectionPointLoad(const char *name);
 extern void InjectionPointRun(const char *name);
 extern bool InjectionPointDetach(const char *name);
 
index dd9db06e10bdf9b1f089b99f5a752e1fadeb58c9..2f60da900bb0cbb4d8bf8d358898be1b2c1024ab 100644 (file)
@@ -128,6 +128,38 @@ SELECT injection_points_detach('TestInjectionLog2');
  
 (1 row)
 
+-- Loading
+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_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 
index c16a33b08dbc87e436d2204d41b04e748a483503..e275c2cf5b6d16b5129bfb37fc397af2c59b6ccb 100644 (file)
@@ -14,6 +14,16 @@ 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()
 --
index 1b695a1820321f27a70b6e68144a31b5a0747a37..b6c8e89324625c1031e578825a74bb2b79df3240 100644 (file)
@@ -302,6 +302,23 @@ injection_points_attach(PG_FUNCTION_ARGS)
    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();
+}
+
 /*
  * SQL function for triggering an injection point.
  */
index 71e2972a7e49800d41bd4f7af768de9ccc0ff1f3..fabf0a8823b5976fb6282cf5634c7dd47296b501 100644 (file)
@@ -41,6 +41,13 @@ SELECT injection_points_detach('TestInjectionLog'); -- fails
 SELECT injection_points_run('TestInjectionLog2'); -- notice
 SELECT injection_points_detach('TestInjectionLog2');
 
+-- Loading
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
+SELECT injection_points_attach('TestInjectionLogLoad', 'notice');
+SELECT injection_points_load('TestInjectionLogLoad'); -- nothing happens
+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.