1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-13 14:22:43 +03:00

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

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