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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user