Support loading of injection points

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/ZkrBE1e2q2wGvsoN@paquier.xyz
This commit is contained in:
Michael Paquier 2024-07-05 17:41:49 +09:00
parent 98347b5a3a
commit 4b211003ec
7 changed files with 168 additions and 36 deletions

View 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:

View 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

View File

@ -15,8 +15,10 @@
* 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);

View 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

View 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()
--

View 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.
*/

View 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.