mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +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:
parent
98347b5a3a
commit
4b211003ec
@ -3618,6 +3618,20 @@ INJECTION_POINT(name);
|
|||||||
their own code using the same macro.
|
their own code using the same macro.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
Add-ins can attach callbacks to an already-declared injection point by
|
Add-ins can attach callbacks to an already-declared injection point by
|
||||||
calling:
|
calling:
|
||||||
|
@ -129,20 +129,47 @@ injection_point_cache_remove(const char *name)
|
|||||||
(void) hash_search(InjectionPointCache, name, HASH_REMOVE, NULL);
|
(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
|
* injection_point_cache_get
|
||||||
*
|
*
|
||||||
* Retrieve an injection point from the local cache, if any.
|
* Retrieve an injection point from the local cache, if any.
|
||||||
*/
|
*/
|
||||||
static InjectionPointCallback
|
static InjectionPointCacheEntry *
|
||||||
injection_point_cache_get(const char *name, const void **private_data)
|
injection_point_cache_get(const char *name)
|
||||||
{
|
{
|
||||||
bool found;
|
bool found;
|
||||||
InjectionPointCacheEntry *entry;
|
InjectionPointCacheEntry *entry;
|
||||||
|
|
||||||
if (private_data)
|
|
||||||
*private_data = NULL;
|
|
||||||
|
|
||||||
/* no callback if no cache yet */
|
/* no callback if no cache yet */
|
||||||
if (InjectionPointCache == NULL)
|
if (InjectionPointCache == NULL)
|
||||||
return 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);
|
hash_search(InjectionPointCache, name, HASH_FIND, &found);
|
||||||
|
|
||||||
if (found)
|
if (found)
|
||||||
{
|
return entry;
|
||||||
if (private_data)
|
|
||||||
*private_data = entry->private_data;
|
|
||||||
return entry->callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
@ -278,6 +301,52 @@ InjectionPointDetach(const char *name)
|
|||||||
#endif
|
#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.
|
* Execute an injection point, if defined.
|
||||||
*
|
*
|
||||||
@ -290,8 +359,7 @@ InjectionPointRun(const char *name)
|
|||||||
#ifdef USE_INJECTION_POINTS
|
#ifdef USE_INJECTION_POINTS
|
||||||
InjectionPointEntry *entry_by_name;
|
InjectionPointEntry *entry_by_name;
|
||||||
bool found;
|
bool found;
|
||||||
InjectionPointCallback injection_callback;
|
InjectionPointCacheEntry *cache_entry;
|
||||||
const void *private_data;
|
|
||||||
|
|
||||||
LWLockAcquire(InjectionPointLock, LW_SHARED);
|
LWLockAcquire(InjectionPointLock, LW_SHARED);
|
||||||
entry_by_name = (InjectionPointEntry *)
|
entry_by_name = (InjectionPointEntry *)
|
||||||
@ -313,37 +381,18 @@ InjectionPointRun(const char *name)
|
|||||||
* Check if the callback exists in the local cache, to avoid unnecessary
|
* Check if the callback exists in the local cache, to avoid unnecessary
|
||||||
* external loads.
|
* external loads.
|
||||||
*/
|
*/
|
||||||
if (injection_point_cache_get(name, NULL) == NULL)
|
if (injection_point_cache_get(name) == NULL)
|
||||||
{
|
{
|
||||||
char path[MAXPGPATH];
|
/* not found in local cache, so load and register it */
|
||||||
InjectionPointCallback injection_callback_local;
|
injection_point_cache_load(entry_by_name);
|
||||||
|
|
||||||
/* 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Now loaded, so get it. */
|
/* Now loaded, so get it. */
|
||||||
injection_callback = injection_point_cache_get(name, &private_data);
|
cache_entry = injection_point_cache_get(name);
|
||||||
|
|
||||||
LWLockRelease(InjectionPointLock);
|
LWLockRelease(InjectionPointLock);
|
||||||
|
|
||||||
injection_callback(name, private_data);
|
cache_entry->callback(name, cache_entry->private_data);
|
||||||
#else
|
#else
|
||||||
elog(ERROR, "Injection points are not supported by this build");
|
elog(ERROR, "Injection points are not supported by this build");
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,8 +15,10 @@
|
|||||||
* Injections points require --enable-injection-points.
|
* Injections points require --enable-injection-points.
|
||||||
*/
|
*/
|
||||||
#ifdef USE_INJECTION_POINTS
|
#ifdef USE_INJECTION_POINTS
|
||||||
|
#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
|
||||||
#define INJECTION_POINT(name) InjectionPointRun(name)
|
#define INJECTION_POINT(name) InjectionPointRun(name)
|
||||||
#else
|
#else
|
||||||
|
#define INJECTION_POINT_LOAD(name) ((void) name)
|
||||||
#define INJECTION_POINT(name) ((void) name)
|
#define INJECTION_POINT(name) ((void) name)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -34,6 +36,7 @@ extern void InjectionPointAttach(const char *name,
|
|||||||
const char *function,
|
const char *function,
|
||||||
const void *private_data,
|
const void *private_data,
|
||||||
int private_data_size);
|
int private_data_size);
|
||||||
|
extern void InjectionPointLoad(const char *name);
|
||||||
extern void InjectionPointRun(const char *name);
|
extern void InjectionPointRun(const char *name);
|
||||||
extern bool InjectionPointDetach(const char *name);
|
extern bool InjectionPointDetach(const char *name);
|
||||||
|
|
||||||
|
@ -128,6 +128,38 @@ SELECT injection_points_detach('TestInjectionLog2');
|
|||||||
|
|
||||||
(1 row)
|
(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
|
-- Runtime conditions
|
||||||
SELECT injection_points_attach('TestConditionError', 'error');
|
SELECT injection_points_attach('TestConditionError', 'error');
|
||||||
injection_points_attach
|
injection_points_attach
|
||||||
|
@ -14,6 +14,16 @@ RETURNS void
|
|||||||
AS 'MODULE_PATHNAME', 'injection_points_attach'
|
AS 'MODULE_PATHNAME', 'injection_points_attach'
|
||||||
LANGUAGE C STRICT PARALLEL UNSAFE;
|
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()
|
-- injection_points_run()
|
||||||
--
|
--
|
||||||
|
@ -302,6 +302,23 @@ injection_points_attach(PG_FUNCTION_ARGS)
|
|||||||
PG_RETURN_VOID();
|
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.
|
* SQL function for triggering an injection point.
|
||||||
*/
|
*/
|
||||||
|
@ -41,6 +41,13 @@ SELECT injection_points_detach('TestInjectionLog'); -- fails
|
|||||||
SELECT injection_points_run('TestInjectionLog2'); -- notice
|
SELECT injection_points_run('TestInjectionLog2'); -- notice
|
||||||
SELECT injection_points_detach('TestInjectionLog2');
|
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
|
-- Runtime conditions
|
||||||
SELECT injection_points_attach('TestConditionError', 'error');
|
SELECT injection_points_attach('TestConditionError', 'error');
|
||||||
-- Any follow-up injection point attached will be local to this process.
|
-- Any follow-up injection point attached will be local to this process.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user