Introduce private data area for injection points
authorMichael Paquier <[email protected]>
Sun, 12 May 2024 09:53:06 +0000 (18:53 +0900)
committerMichael Paquier <[email protected]>
Sun, 12 May 2024 09:53:06 +0000 (18:53 +0900)
This commit extends the backend-side infrastructure of injection points
so as it becomes possible to register some input data when attaching a
point.  This private data can be registered with the function name and
the library name of the callback when attaching a point, then it is
given as input argument to the callback.  This gives the possibility for
modules to pass down custom data at runtime when attaching a point
without managing that internally, in a manner consistent with the
callback entry retrieved from the hash shmem table storing the injection
point data.

InjectionPointAttach() gains two arguments, to be able to define the
private data contents and its size.

A follow-up commit will rely on this infrastructure to close a race
condition with the injection point detach in the module
injection_points.

While on it, this changes InjectionPointDetach() to return a boolean,
returning false if a point cannot be detached.  This has been mentioned
by Noah as useful when it comes to implement more complex tests with
concurrent point detach, solid with the automatic detach done for local
points in the test module.

Documentation is adjusted in consequence.

Per discussion with Noah Misch.

Reviewed-by: Noah Misch
Discussion: https://postgr.es/m/20240509031553[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.c

index 7d053698a2b6f25b408ee5eddbec7d81c1765d8d..a7c170476af8e083715c80fca9cd7ce1883bb0c3 100644 (file)
@@ -3624,12 +3624,16 @@ INJECTION_POINT(name);
 <programlisting>
 extern void InjectionPointAttach(const char *name,
                                  const char *library,
-                                 const char *function);
+                                 const char *function,
+                                 const void *private_data,
+                                 int private_data_size);
 </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>private_data</literal>
+     is a private area of data of size <literal>private_data_size</literal>
+     given as argument to the callback when executed.
     </para>
 
     <para>
@@ -3637,7 +3641,7 @@ extern void InjectionPointAttach(const char *name,
      <literal>InjectionPointCallback</literal>:
 <programlisting>
 static void
-custom_injection_callback(const char *name)
+custom_injection_callback(const char *name, const void *private_data)
 {
     elog(NOTICE, "%s: executed custom callback", name);
 }
@@ -3650,8 +3654,10 @@ custom_injection_callback(const char *name)
     <para>
      Optionally, it is possible to detach an injection point by calling:
 <programlisting>
-extern void InjectionPointDetach(const char *name);
+extern bool InjectionPointDetach(const char *name);
 </programlisting>
+     On success, <literal>true</literal> is returned, <literal>false</literal>
+     otherwise.
     </para>
 
     <para>
index 0cf4d51cac48cb03e966d8a1de7f70c75d86aa0e..5c2a0d2297e206ab02be72f2d2b4322c10fb598c 100644 (file)
@@ -42,6 +42,7 @@ static HTAB *InjectionPointHash;  /* find points from names */
 #define INJ_NAME_MAXLEN        64
 #define INJ_LIB_MAXLEN     128
 #define INJ_FUNC_MAXLEN        128
+#define INJ_PRIVATE_MAXLEN 1024
 
 /* Single injection point stored in InjectionPointHash */
 typedef struct InjectionPointEntry
@@ -49,6 +50,12 @@ typedef struct InjectionPointEntry
    char        name[INJ_NAME_MAXLEN];  /* hash key */
    char        library[INJ_LIB_MAXLEN];    /* library */
    char        function[INJ_FUNC_MAXLEN];  /* function */
+
+   /*
+    * Opaque data area that modules can use to pass some custom data to
+    * callbacks, registered when attached.
+    */
+   char        private_data[INJ_PRIVATE_MAXLEN];
 } InjectionPointEntry;
 
 #define INJECTION_POINT_HASH_INIT_SIZE 16
@@ -61,6 +68,7 @@ typedef struct InjectionPointEntry
 typedef struct InjectionPointCacheEntry
 {
    char        name[INJ_NAME_MAXLEN];
+   char        private_data[INJ_PRIVATE_MAXLEN];
    InjectionPointCallback callback;
 } InjectionPointCacheEntry;
 
@@ -73,7 +81,8 @@ static HTAB *InjectionPointCache = NULL;
  */
 static void
 injection_point_cache_add(const char *name,
-                         InjectionPointCallback callback)
+                         InjectionPointCallback callback,
+                         const void *private_data)
 {
    InjectionPointCacheEntry *entry;
    bool        found;
@@ -99,6 +108,8 @@ injection_point_cache_add(const char *name,
    Assert(!found);
    strlcpy(entry->name, name, sizeof(entry->name));
    entry->callback = callback;
+   if (private_data != NULL)
+       memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
 }
 
 /*
@@ -124,11 +135,14 @@ 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)
+injection_point_cache_get(const char *name, const void **private_data)
 {
    bool        found;
    InjectionPointCacheEntry *entry;
 
+   if (private_data)
+       *private_data = NULL;
+
    /* no callback if no cache yet */
    if (InjectionPointCache == NULL)
        return NULL;
@@ -137,7 +151,11 @@ injection_point_cache_get(const char *name)
        hash_search(InjectionPointCache, name, HASH_FIND, &found);
 
    if (found)
+   {
+       if (private_data)
+           *private_data = entry->private_data;
        return entry->callback;
+   }
 
    return NULL;
 }
@@ -186,7 +204,9 @@ InjectionPointShmemInit(void)
 void
 InjectionPointAttach(const char *name,
                     const char *library,
-                    const char *function)
+                    const char *function,
+                    const void *private_data,
+                    int private_data_size)
 {
 #ifdef USE_INJECTION_POINTS
    InjectionPointEntry *entry_by_name;
@@ -201,6 +221,9 @@ InjectionPointAttach(const char *name,
    if (strlen(function) >= INJ_FUNC_MAXLEN)
        elog(ERROR, "injection point function %s too long (maximum of %u)",
             function, INJ_FUNC_MAXLEN);
+   if (private_data_size >= INJ_PRIVATE_MAXLEN)
+       elog(ERROR, "injection point data too long (maximum of %u)",
+            INJ_PRIVATE_MAXLEN);
 
    /*
     * Allocate and register a new injection point.  A new point should not
@@ -223,6 +246,8 @@ 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';
+   if (private_data != NULL)
+       memcpy(entry_by_name->private_data, private_data, private_data_size);
 
    LWLockRelease(InjectionPointLock);
 
@@ -233,8 +258,10 @@ InjectionPointAttach(const char *name,
 
 /*
  * Detach an existing injection point.
+ *
+ * Returns true if the injection point was detached, false otherwise.
  */
-void
+bool
 InjectionPointDetach(const char *name)
 {
 #ifdef USE_INJECTION_POINTS
@@ -245,10 +272,12 @@ InjectionPointDetach(const char *name)
    LWLockRelease(InjectionPointLock);
 
    if (!found)
-       elog(ERROR, "injection point \"%s\" not found", name);
+       return false;
 
+   return true;
 #else
    elog(ERROR, "Injection points are not supported by this build");
+   return true;                /* silence compiler */
 #endif
 }
 
@@ -265,6 +294,7 @@ InjectionPointRun(const char *name)
    InjectionPointEntry *entry_by_name;
    bool        found;
    InjectionPointCallback injection_callback;
+   const void *private_data;
 
    LWLockAcquire(InjectionPointLock, LW_SHARED);
    entry_by_name = (InjectionPointEntry *)
@@ -286,10 +316,10 @@ InjectionPointRun(const char *name)
     * 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)
+   if (injection_point_cache_get(name, NULL) == 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,
@@ -299,18 +329,21 @@ InjectionPointRun(const char *name)
            elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
                 path, name);
 
-       injection_callback = (InjectionPointCallback)
+       injection_callback_local = (InjectionPointCallback)
            load_external_function(path, entry_by_name->function, false, NULL);
 
-       if (injection_callback == 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);
+       injection_point_cache_add(name, injection_callback_local,
+                                 entry_by_name->private_data);
    }
 
-   injection_callback(name);
+   /* Now loaded, so get it. */
+   injection_callback = injection_point_cache_get(name, &private_data);
+   injection_callback(name, private_data);
 #else
    elog(ERROR, "Injection points are not supported by this build");
 #endif
index 55524b568ff97f216305637a4d837b7637b7e72f..a61d5d44391d75056bb43870031f0eb8d8c21a40 100644 (file)
 /*
  * Typedef for callback function launched by an injection point.
  */
-typedef void (*InjectionPointCallback) (const char *name);
+typedef void (*InjectionPointCallback) (const char *name,
+                                       const void *private_data);
 
 extern Size InjectionPointShmemSize(void);
 extern void InjectionPointShmemInit(void);
 
 extern void InjectionPointAttach(const char *name,
                                 const char *library,
-                                const char *function);
+                                const char *function,
+                                const void *private_data,
+                                int private_data_size);
 extern void InjectionPointRun(const char *name);
-extern void InjectionPointDetach(const char *name);
+extern bool InjectionPointDetach(const char *name);
 
 #endif                         /* INJECTION_POINT_H */
index 1341d66c927e8211816ba82c1cfedfe6639f3282..dd9db06e10bdf9b1f089b99f5a752e1fadeb58c9 100644 (file)
@@ -114,7 +114,7 @@ NOTICE:  notice triggered for injection point TestInjectionLog2
 (1 row)
 
 SELECT injection_points_detach('TestInjectionLog'); -- fails
-ERROR:  injection point "TestInjectionLog" not found
+ERROR:  could not detach injection point "TestInjectionLog"
 SELECT injection_points_run('TestInjectionLog2'); -- notice
 NOTICE:  notice triggered for injection point TestInjectionLog2
  injection_points_run 
index a74a4a28afda02aca351b63669b68ad3c6fddf7e..3126c8117c60dc6a8de641ea4117975bcb46d6c0 100644 (file)
@@ -73,9 +73,12 @@ typedef struct InjectionPointSharedState
 /* Pointer to shared-memory state. */
 static InjectionPointSharedState *inj_state = NULL;
 
-extern PGDLLEXPORT void injection_error(const char *name);
-extern PGDLLEXPORT void injection_notice(const char *name);
-extern PGDLLEXPORT void injection_wait(const char *name);
+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_wait(const char *name,
+                                      const void *private_data);
 
 /* track if injection points attached in this process are linked to it */
 static bool injection_point_local = false;
@@ -189,7 +192,7 @@ injection_points_cleanup(int code, Datum arg)
 
    /* Detach, without holding the spinlock */
    for (int i = 0; i < count; i++)
-       InjectionPointDetach(names[i]);
+       (void) InjectionPointDetach(names[i]);
 
    /* Clear all the conditions */
    SpinLockAcquire(&inj_state->lock);
@@ -211,7 +214,7 @@ injection_points_cleanup(int code, Datum arg)
 
 /* Set of callbacks available to be attached to an injection point. */
 void
-injection_error(const char *name)
+injection_error(const char *name, const void *private_data)
 {
    if (!injection_point_allowed(name))
        return;
@@ -220,7 +223,7 @@ injection_error(const char *name)
 }
 
 void
-injection_notice(const char *name)
+injection_notice(const char *name, const void *private_data)
 {
    if (!injection_point_allowed(name))
        return;
@@ -230,7 +233,7 @@ injection_notice(const char *name)
 
 /* Wait on a condition variable, awaken by injection_points_wakeup() */
 void
-injection_wait(const char *name)
+injection_wait(const char *name, const void *private_data)
 {
    uint32      old_wait_counts = 0;
    int         index = -1;
@@ -311,7 +314,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
    else
        elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
 
-   InjectionPointAttach(name, "injection_points", function);
+   InjectionPointAttach(name, "injection_points", function, NULL, 0);
 
    if (injection_point_local)
    {
@@ -430,7 +433,8 @@ injection_points_detach(PG_FUNCTION_ARGS)
 {
    char       *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
 
-   InjectionPointDetach(name);
+   if (!InjectionPointDetach(name))
+       elog(ERROR, "could not detach injection point \"%s\"", name);
 
    if (inj_state == NULL)
        injection_init_shmem();