1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-04 20:11:56 +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