Add INJECTION_POINT_CACHED() to run injection points directly from cache

This new macro is able to perform a direct lookup from the local cache
of injection points (refreshed each time a point is loaded or run),
without touching the shared memory state of injection points at all.

This works in combination with INJECTION_POINT_LOAD(), and it is better
than INJECTION_POINT() in a critical section due to the fact that it
would avoid all memory allocations should a concurrent detach happen
since a LOAD(), as it retrieves a callback from the backend-private
memory.

The documentation is updated to describe in more details how to use this
new macro with a load.  Some tests are added to the module
injection_points based on a new SQL function that acts as a wrapper of
INJECTION_POINT_CACHED().

Based on a suggestion from Heikki Linnakangas.

Author: Heikki Linnakangas, Michael Paquier
Discussion: https://postgr.es/m/58d588d0-e63f-432f-9181-bed29313dece@iki.fi
This commit is contained in:
Michael Paquier 2024-07-18 09:50:41 +09:00
parent 6159331acf
commit a0a5869a85
7 changed files with 69 additions and 7 deletions

View File

@ -3619,17 +3619,20 @@ INJECTION_POINT(name);
</para>
<para>
An injection point with a given <literal>name</literal> can be loaded
using macro:
Executing an injection point can require allocating a small amount of
memory, which can fail. If you need to have an injection point in a
critical section where dynamic allocations are not allowed, you can use
a two-step approach with the following macros:
<programlisting>
INJECTION_POINT_LOAD(name);
INJECTION_POINT_CACHED(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.
Before entering the critical section,
call <function>INJECTION_POINT_LOAD</function>. It checks the shared
memory state, and loads the callback into backend-private memory if it is
active. Inside the critical section, use
<function>INJECTION_POINT_CACHED</function> to execute the callback.
</para>
<para>

View File

@ -553,3 +553,20 @@ InjectionPointRun(const char *name)
elog(ERROR, "Injection points are not supported by this build");
#endif
}
/*
* Execute an injection point directly from the cache, if defined.
*/
void
InjectionPointCached(const char *name)
{
#ifdef USE_INJECTION_POINTS
InjectionPointCacheEntry *cache_entry;
cache_entry = injection_point_cache_get(name);
if (cache_entry)
cache_entry->callback(name, cache_entry->private_data);
#else
elog(ERROR, "Injection points are not supported by this build");
#endif
}

View File

@ -17,9 +17,11 @@
#ifdef USE_INJECTION_POINTS
#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name)
#define INJECTION_POINT(name) InjectionPointRun(name)
#define INJECTION_POINT_CACHED(name) InjectionPointCached(name)
#else
#define INJECTION_POINT_LOAD(name) ((void) name)
#define INJECTION_POINT(name) ((void) name)
#define INJECTION_POINT_CACHED(name) ((void) name)
#endif
/*
@ -38,6 +40,7 @@ extern void InjectionPointAttach(const char *name,
int private_data_size);
extern void InjectionPointLoad(const char *name);
extern void InjectionPointRun(const char *name);
extern void InjectionPointCached(const char *name);
extern bool InjectionPointDetach(const char *name);
#endif /* INJECTION_POINT_H */

View File

@ -129,6 +129,12 @@ SELECT injection_points_detach('TestInjectionLog2');
(1 row)
-- Loading
SELECT injection_points_cached('TestInjectionLogLoad'); -- nothing in cache
injection_points_cached
-------------------------
(1 row)
SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
injection_points_load
-----------------------
@ -147,6 +153,13 @@ SELECT injection_points_load('TestInjectionLogLoad'); -- nothing happens
(1 row)
SELECT injection_points_cached('TestInjectionLogLoad'); -- runs from cache
NOTICE: notice triggered for injection point TestInjectionLogLoad
injection_points_cached
-------------------------
(1 row)
SELECT injection_points_run('TestInjectionLogLoad'); -- runs from cache
NOTICE: notice triggered for injection point TestInjectionLogLoad
injection_points_run

View File

@ -34,6 +34,16 @@ RETURNS void
AS 'MODULE_PATHNAME', 'injection_points_run'
LANGUAGE C STRICT PARALLEL UNSAFE;
--
-- injection_points_cached()
--
-- Executes the action attached to the injection point, from local cache.
--
CREATE FUNCTION injection_points_cached(IN point_name TEXT)
RETURNS void
AS 'MODULE_PATHNAME', 'injection_points_cached'
LANGUAGE C STRICT PARALLEL UNSAFE;
--
-- injection_points_wakeup()
--

View File

@ -333,6 +333,20 @@ injection_points_run(PG_FUNCTION_ARGS)
PG_RETURN_VOID();
}
/*
* SQL function for triggering an injection point from cache.
*/
PG_FUNCTION_INFO_V1(injection_points_cached);
Datum
injection_points_cached(PG_FUNCTION_ARGS)
{
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
INJECTION_POINT_CACHED(name);
PG_RETURN_VOID();
}
/*
* SQL function for waking up an injection point waiting in injection_wait().
*/

View File

@ -42,9 +42,11 @@ SELECT injection_points_run('TestInjectionLog2'); -- notice
SELECT injection_points_detach('TestInjectionLog2');
-- Loading
SELECT injection_points_cached('TestInjectionLogLoad'); -- nothing in cache
SELECT injection_points_load('TestInjectionLogLoad'); -- nothing
SELECT injection_points_attach('TestInjectionLogLoad', 'notice');
SELECT injection_points_load('TestInjectionLogLoad'); -- nothing happens
SELECT injection_points_cached('TestInjectionLogLoad'); -- runs from cache
SELECT injection_points_run('TestInjectionLogLoad'); -- runs from cache
SELECT injection_points_detach('TestInjectionLogLoad');