Introduce private data area for injection points
This commit extends the backend-side infrastructure of injection points so as it becomes possible to register some input data when attaching a point. This private data can be registered with the function name and the library name of the callback when attaching a point, then it is given as input argument to the callback. This gives the possibility for modules to pass down custom data at runtime when attaching a point without managing that internally, in a manner consistent with the callback entry retrieved from the hash shmem table storing the injection point data. InjectionPointAttach() gains two arguments, to be able to define the private data contents and its size. A follow-up commit will rely on this infrastructure to close a race condition with the injection point detach in the module injection_points. While on it, this changes InjectionPointDetach() to return a boolean, returning false if a point cannot be detached. This has been mentioned by Noah as useful when it comes to implement more complex tests with concurrent point detach, solid with the automatic detach done for local points in the test module. Documentation is adjusted in consequence. Per discussion with Noah Misch. Reviewed-by: Noah Misch Discussion: https://postgr.es/m/20240509031553.47@rfd.leadboat.com
This commit is contained in:
parent
407e0b023c
commit
33181b48fd
@ -3624,12 +3624,16 @@ INJECTION_POINT(name);
|
||||
<programlisting>
|
||||
extern void InjectionPointAttach(const char *name,
|
||||
const char *library,
|
||||
const char *function);
|
||||
const char *function,
|
||||
const void *private_data,
|
||||
int private_data_size);
|
||||
</programlisting>
|
||||
|
||||
<literal>name</literal> is the name of the injection point, which when
|
||||
reached during execution will execute the <literal>function</literal>
|
||||
loaded from <literal>library</literal>.
|
||||
loaded from <literal>library</literal>. <literal>private_data</literal>
|
||||
is a private area of data of size <literal>private_data_size</literal>
|
||||
given as argument to the callback when executed.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
@ -3637,7 +3641,7 @@ extern void InjectionPointAttach(const char *name,
|
||||
<literal>InjectionPointCallback</literal>:
|
||||
<programlisting>
|
||||
static void
|
||||
custom_injection_callback(const char *name)
|
||||
custom_injection_callback(const char *name, const void *private_data)
|
||||
{
|
||||
elog(NOTICE, "%s: executed custom callback", name);
|
||||
}
|
||||
@ -3650,8 +3654,10 @@ custom_injection_callback(const char *name)
|
||||
<para>
|
||||
Optionally, it is possible to detach an injection point by calling:
|
||||
<programlisting>
|
||||
extern void InjectionPointDetach(const char *name);
|
||||
extern bool InjectionPointDetach(const char *name);
|
||||
</programlisting>
|
||||
On success, <literal>true</literal> is returned, <literal>false</literal>
|
||||
otherwise.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -42,6 +42,7 @@ static HTAB *InjectionPointHash; /* find points from names */
|
||||
#define INJ_NAME_MAXLEN 64
|
||||
#define INJ_LIB_MAXLEN 128
|
||||
#define INJ_FUNC_MAXLEN 128
|
||||
#define INJ_PRIVATE_MAXLEN 1024
|
||||
|
||||
/* Single injection point stored in InjectionPointHash */
|
||||
typedef struct InjectionPointEntry
|
||||
@ -49,6 +50,12 @@ typedef struct InjectionPointEntry
|
||||
char name[INJ_NAME_MAXLEN]; /* hash key */
|
||||
char library[INJ_LIB_MAXLEN]; /* library */
|
||||
char function[INJ_FUNC_MAXLEN]; /* function */
|
||||
|
||||
/*
|
||||
* Opaque data area that modules can use to pass some custom data to
|
||||
* callbacks, registered when attached.
|
||||
*/
|
||||
char private_data[INJ_PRIVATE_MAXLEN];
|
||||
} InjectionPointEntry;
|
||||
|
||||
#define INJECTION_POINT_HASH_INIT_SIZE 16
|
||||
@ -61,6 +68,7 @@ typedef struct InjectionPointEntry
|
||||
typedef struct InjectionPointCacheEntry
|
||||
{
|
||||
char name[INJ_NAME_MAXLEN];
|
||||
char private_data[INJ_PRIVATE_MAXLEN];
|
||||
InjectionPointCallback callback;
|
||||
} InjectionPointCacheEntry;
|
||||
|
||||
@ -73,7 +81,8 @@ static HTAB *InjectionPointCache = NULL;
|
||||
*/
|
||||
static void
|
||||
injection_point_cache_add(const char *name,
|
||||
InjectionPointCallback callback)
|
||||
InjectionPointCallback callback,
|
||||
const void *private_data)
|
||||
{
|
||||
InjectionPointCacheEntry *entry;
|
||||
bool found;
|
||||
@ -99,6 +108,8 @@ injection_point_cache_add(const char *name,
|
||||
Assert(!found);
|
||||
strlcpy(entry->name, name, sizeof(entry->name));
|
||||
entry->callback = callback;
|
||||
if (private_data != NULL)
|
||||
memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -124,11 +135,14 @@ injection_point_cache_remove(const char *name)
|
||||
* Retrieve an injection point from the local cache, if any.
|
||||
*/
|
||||
static InjectionPointCallback
|
||||
injection_point_cache_get(const char *name)
|
||||
injection_point_cache_get(const char *name, const void **private_data)
|
||||
{
|
||||
bool found;
|
||||
InjectionPointCacheEntry *entry;
|
||||
|
||||
if (private_data)
|
||||
*private_data = NULL;
|
||||
|
||||
/* no callback if no cache yet */
|
||||
if (InjectionPointCache == NULL)
|
||||
return NULL;
|
||||
@ -137,7 +151,11 @@ injection_point_cache_get(const char *name)
|
||||
hash_search(InjectionPointCache, name, HASH_FIND, &found);
|
||||
|
||||
if (found)
|
||||
{
|
||||
if (private_data)
|
||||
*private_data = entry->private_data;
|
||||
return entry->callback;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@ -186,7 +204,9 @@ InjectionPointShmemInit(void)
|
||||
void
|
||||
InjectionPointAttach(const char *name,
|
||||
const char *library,
|
||||
const char *function)
|
||||
const char *function,
|
||||
const void *private_data,
|
||||
int private_data_size)
|
||||
{
|
||||
#ifdef USE_INJECTION_POINTS
|
||||
InjectionPointEntry *entry_by_name;
|
||||
@ -201,6 +221,9 @@ InjectionPointAttach(const char *name,
|
||||
if (strlen(function) >= INJ_FUNC_MAXLEN)
|
||||
elog(ERROR, "injection point function %s too long (maximum of %u)",
|
||||
function, INJ_FUNC_MAXLEN);
|
||||
if (private_data_size >= INJ_PRIVATE_MAXLEN)
|
||||
elog(ERROR, "injection point data too long (maximum of %u)",
|
||||
INJ_PRIVATE_MAXLEN);
|
||||
|
||||
/*
|
||||
* Allocate and register a new injection point. A new point should not
|
||||
@ -223,6 +246,8 @@ InjectionPointAttach(const char *name,
|
||||
entry_by_name->library[INJ_LIB_MAXLEN - 1] = '\0';
|
||||
strlcpy(entry_by_name->function, function, sizeof(entry_by_name->function));
|
||||
entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0';
|
||||
if (private_data != NULL)
|
||||
memcpy(entry_by_name->private_data, private_data, private_data_size);
|
||||
|
||||
LWLockRelease(InjectionPointLock);
|
||||
|
||||
@ -233,8 +258,10 @@ InjectionPointAttach(const char *name,
|
||||
|
||||
/*
|
||||
* Detach an existing injection point.
|
||||
*
|
||||
* Returns true if the injection point was detached, false otherwise.
|
||||
*/
|
||||
void
|
||||
bool
|
||||
InjectionPointDetach(const char *name)
|
||||
{
|
||||
#ifdef USE_INJECTION_POINTS
|
||||
@ -245,10 +272,12 @@ InjectionPointDetach(const char *name)
|
||||
LWLockRelease(InjectionPointLock);
|
||||
|
||||
if (!found)
|
||||
elog(ERROR, "injection point \"%s\" not found", name);
|
||||
return false;
|
||||
|
||||
return true;
|
||||
#else
|
||||
elog(ERROR, "Injection points are not supported by this build");
|
||||
return true; /* silence compiler */
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -265,6 +294,7 @@ InjectionPointRun(const char *name)
|
||||
InjectionPointEntry *entry_by_name;
|
||||
bool found;
|
||||
InjectionPointCallback injection_callback;
|
||||
const void *private_data;
|
||||
|
||||
LWLockAcquire(InjectionPointLock, LW_SHARED);
|
||||
entry_by_name = (InjectionPointEntry *)
|
||||
@ -286,10 +316,10 @@ InjectionPointRun(const char *name)
|
||||
* Check if the callback exists in the local cache, to avoid unnecessary
|
||||
* external loads.
|
||||
*/
|
||||
injection_callback = injection_point_cache_get(name);
|
||||
if (injection_callback == NULL)
|
||||
if (injection_point_cache_get(name, NULL) == 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,
|
||||
@ -299,18 +329,21 @@ InjectionPointRun(const char *name)
|
||||
elog(ERROR, "could not find library \"%s\" for injection point \"%s\"",
|
||||
path, name);
|
||||
|
||||
injection_callback = (InjectionPointCallback)
|
||||
injection_callback_local = (InjectionPointCallback)
|
||||
load_external_function(path, entry_by_name->function, false, NULL);
|
||||
|
||||
if (injection_callback == 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);
|
||||
injection_point_cache_add(name, injection_callback_local,
|
||||
entry_by_name->private_data);
|
||||
}
|
||||
|
||||
injection_callback(name);
|
||||
/* Now loaded, so get it. */
|
||||
injection_callback = injection_point_cache_get(name, &private_data);
|
||||
injection_callback(name, private_data);
|
||||
#else
|
||||
elog(ERROR, "Injection points are not supported by this build");
|
||||
#endif
|
||||
|
@ -23,15 +23,18 @@
|
||||
/*
|
||||
* Typedef for callback function launched by an injection point.
|
||||
*/
|
||||
typedef void (*InjectionPointCallback) (const char *name);
|
||||
typedef void (*InjectionPointCallback) (const char *name,
|
||||
const void *private_data);
|
||||
|
||||
extern Size InjectionPointShmemSize(void);
|
||||
extern void InjectionPointShmemInit(void);
|
||||
|
||||
extern void InjectionPointAttach(const char *name,
|
||||
const char *library,
|
||||
const char *function);
|
||||
const char *function,
|
||||
const void *private_data,
|
||||
int private_data_size);
|
||||
extern void InjectionPointRun(const char *name);
|
||||
extern void InjectionPointDetach(const char *name);
|
||||
extern bool InjectionPointDetach(const char *name);
|
||||
|
||||
#endif /* INJECTION_POINT_H */
|
||||
|
@ -114,7 +114,7 @@ NOTICE: notice triggered for injection point TestInjectionLog2
|
||||
(1 row)
|
||||
|
||||
SELECT injection_points_detach('TestInjectionLog'); -- fails
|
||||
ERROR: injection point "TestInjectionLog" not found
|
||||
ERROR: could not detach injection point "TestInjectionLog"
|
||||
SELECT injection_points_run('TestInjectionLog2'); -- notice
|
||||
NOTICE: notice triggered for injection point TestInjectionLog2
|
||||
injection_points_run
|
||||
|
@ -73,9 +73,12 @@ typedef struct InjectionPointSharedState
|
||||
/* Pointer to shared-memory state. */
|
||||
static InjectionPointSharedState *inj_state = NULL;
|
||||
|
||||
extern PGDLLEXPORT void injection_error(const char *name);
|
||||
extern PGDLLEXPORT void injection_notice(const char *name);
|
||||
extern PGDLLEXPORT void injection_wait(const char *name);
|
||||
extern PGDLLEXPORT void injection_error(const char *name,
|
||||
const void *private_data);
|
||||
extern PGDLLEXPORT void injection_notice(const char *name,
|
||||
const void *private_data);
|
||||
extern PGDLLEXPORT void injection_wait(const char *name,
|
||||
const void *private_data);
|
||||
|
||||
/* track if injection points attached in this process are linked to it */
|
||||
static bool injection_point_local = false;
|
||||
@ -189,7 +192,7 @@ injection_points_cleanup(int code, Datum arg)
|
||||
|
||||
/* Detach, without holding the spinlock */
|
||||
for (int i = 0; i < count; i++)
|
||||
InjectionPointDetach(names[i]);
|
||||
(void) InjectionPointDetach(names[i]);
|
||||
|
||||
/* Clear all the conditions */
|
||||
SpinLockAcquire(&inj_state->lock);
|
||||
@ -211,7 +214,7 @@ injection_points_cleanup(int code, Datum arg)
|
||||
|
||||
/* Set of callbacks available to be attached to an injection point. */
|
||||
void
|
||||
injection_error(const char *name)
|
||||
injection_error(const char *name, const void *private_data)
|
||||
{
|
||||
if (!injection_point_allowed(name))
|
||||
return;
|
||||
@ -220,7 +223,7 @@ injection_error(const char *name)
|
||||
}
|
||||
|
||||
void
|
||||
injection_notice(const char *name)
|
||||
injection_notice(const char *name, const void *private_data)
|
||||
{
|
||||
if (!injection_point_allowed(name))
|
||||
return;
|
||||
@ -230,7 +233,7 @@ injection_notice(const char *name)
|
||||
|
||||
/* Wait on a condition variable, awaken by injection_points_wakeup() */
|
||||
void
|
||||
injection_wait(const char *name)
|
||||
injection_wait(const char *name, const void *private_data)
|
||||
{
|
||||
uint32 old_wait_counts = 0;
|
||||
int index = -1;
|
||||
@ -311,7 +314,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
|
||||
else
|
||||
elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
|
||||
|
||||
InjectionPointAttach(name, "injection_points", function);
|
||||
InjectionPointAttach(name, "injection_points", function, NULL, 0);
|
||||
|
||||
if (injection_point_local)
|
||||
{
|
||||
@ -430,7 +433,8 @@ injection_points_detach(PG_FUNCTION_ARGS)
|
||||
{
|
||||
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
|
||||
|
||||
InjectionPointDetach(name);
|
||||
if (!InjectionPointDetach(name))
|
||||
elog(ERROR, "could not detach injection point \"%s\"", name);
|
||||
|
||||
if (inj_state == NULL)
|
||||
injection_init_shmem();
|
||||
|
Loading…
x
Reference in New Issue
Block a user