Hi all, I have a couple of extra toys for injection points in my bucket that I'd like to propose for integration in v18, based on some feedback I have received: 1) Preload an injection point into the backend-level cache without running it. This has come up because an injection point run for the first time needs to be loaded with load_external_function that internally does some allocations, and this would not work if the injection point is in a critical section. Being able to preload an injection point requires an extra macro, called INJECTION_POINT_PRELOAD. Perhaps "load" makes more sense as keyword, here. 2) Grab values at runtime from the code path where an injection point is run and give them to the callback. The case here is to be able to do some dynamic manipulation or a stack, reads of some runtime data or even decide of a different set of actions in a callback based on what the input has provided. One case that I've been playing with here is the dynamic manipulation of pages in specific code paths to stress internal checks, as one example. This introduces a 1-argument version, as multiple args could always be passed down to the callback within a structure.
The in-core module injection_points is extended to provide a SQL interface to be able to do the preloading or define a callback with arguments. The two changes are split into patches of their own. These new facilities could be backpatched if there is a need for them in the future in stable branches, as these are aimed for tests and the changes do not introduce any ABI breakages with the existing APIs or the in-core module. Thoughts and comments are welcome. -- Michael
From ed617725d1e6a156363384ed5fcbf4e79b5f8ab4 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Mon, 20 May 2024 11:50:42 +0900 Subject: [PATCH 1/2] Extend injection points with optional runtime arguments This extends injections points with a 1-argument flavor, so as it becomes possible for callbacks to pass down values coming from the code paths where an injection point is defined. The primary use case that can be covered in this function is runtime manipulation of a given stack, where it would be possible to corrupt a running state, based on a runtime set of conditions. For example, imagine a class of failures in the solar flare category causing bits to flip on a page. --- src/include/utils/injection_point.h | 9 +- src/backend/utils/misc/injection_point.c | 92 +++++++++++++------ .../expected/injection_points.out | 31 +++++++ .../injection_points--1.0.sql | 10 ++ .../injection_points/injection_points.c | 39 +++++++- .../injection_points/sql/injection_points.sql | 9 ++ doc/src/sgml/xfunc.sgml | 9 +- 7 files changed, 169 insertions(+), 30 deletions(-) diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h index a61d5d4439..c2c0840706 100644 --- a/src/include/utils/injection_point.h +++ b/src/include/utils/injection_point.h @@ -16,8 +16,10 @@ */ #ifdef USE_INJECTION_POINTS #define INJECTION_POINT(name) InjectionPointRun(name) +#define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1) #else #define INJECTION_POINT(name) ((void) name) +#define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1) #endif /* @@ -25,6 +27,9 @@ */ typedef void (*InjectionPointCallback) (const char *name, const void *private_data); +typedef void (*InjectionPointCallback1Arg) (const char *name, + const void *private_data, + const void *arg1); extern Size InjectionPointShmemSize(void); extern void InjectionPointShmemInit(void); @@ -33,8 +38,10 @@ extern void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, - int private_data_size); + int private_data_size, + int num_args); extern void InjectionPointRun(const char *name); +extern void InjectionPointRun1Arg(const char *name, void *arg1); extern bool InjectionPointDetach(const char *name); #endif /* INJECTION_POINT_H */ diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c index 5c2a0d2297..2bcdb2708c 100644 --- a/src/backend/utils/misc/injection_point.c +++ b/src/backend/utils/misc/injection_point.c @@ -56,6 +56,9 @@ typedef struct InjectionPointEntry * callbacks, registered when attached. */ char private_data[INJ_PRIVATE_MAXLEN]; + + /* Number of arguments used by the callback */ + int num_args; } InjectionPointEntry; #define INJECTION_POINT_HASH_INIT_SIZE 16 @@ -69,7 +72,12 @@ typedef struct InjectionPointCacheEntry { char name[INJ_NAME_MAXLEN]; char private_data[INJ_PRIVATE_MAXLEN]; - InjectionPointCallback callback; + int num_args; + union + { + InjectionPointCallback callback; + InjectionPointCallback1Arg callback_1arg; + } data; } InjectionPointCacheEntry; static HTAB *InjectionPointCache = NULL; @@ -81,8 +89,9 @@ static HTAB *InjectionPointCache = NULL; */ static void injection_point_cache_add(const char *name, - InjectionPointCallback callback, - const void *private_data) + void *callback, + const void *private_data, + int num_args) { InjectionPointCacheEntry *entry; bool found; @@ -107,7 +116,14 @@ injection_point_cache_add(const char *name, Assert(!found); strlcpy(entry->name, name, sizeof(entry->name)); - entry->callback = callback; + entry->num_args = num_args; + if (num_args == 0) + entry->data.callback = (InjectionPointCallback) callback; + else if (num_args == 1) + entry->data.callback_1arg = (InjectionPointCallback1Arg) callback; + else + elog(ERROR, "unsupported number of arguments for injection point \"%s\"", + name); if (private_data != NULL) memcpy(entry->private_data, private_data, INJ_PRIVATE_MAXLEN); } @@ -134,15 +150,12 @@ 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, 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 +164,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; } @@ -206,7 +215,8 @@ InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, - int private_data_size) + int private_data_size, + int num_args) { #ifdef USE_INJECTION_POINTS InjectionPointEntry *entry_by_name; @@ -248,6 +258,7 @@ InjectionPointAttach(const char *name, entry_by_name->function[INJ_FUNC_MAXLEN - 1] = '\0'; if (private_data != NULL) memcpy(entry_by_name->private_data, private_data, private_data_size); + entry_by_name->num_args = num_args; LWLockRelease(InjectionPointLock); @@ -282,19 +293,18 @@ InjectionPointDetach(const char *name) } /* - * Execute an injection point, if defined. + * Workhorse for execution of an injection point, if defined. * * Check first the shared hash table, and adapt the local cache depending * on that as it could be possible that an entry to run has been removed. */ -void -InjectionPointRun(const char *name) +static inline void +InjectionPointRunInternal(const char *name, int num_args, void *arg1) { #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 *) @@ -316,10 +326,10 @@ 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; + void *injection_callback_local; /* not found in local cache, so load and register */ snprintf(path, MAXPGPATH, "%s/%s%s", pkglib_path, @@ -329,7 +339,7 @@ InjectionPointRun(const char *name) elog(ERROR, "could not find library \"%s\" for injection point \"%s\"", path, name); - injection_callback_local = (InjectionPointCallback) + injection_callback_local = (void *) load_external_function(path, entry_by_name->function, false, NULL); if (injection_callback_local == NULL) @@ -338,13 +348,43 @@ InjectionPointRun(const char *name) /* add it to the local cache when found */ injection_point_cache_add(name, injection_callback_local, - entry_by_name->private_data); + entry_by_name->private_data, + entry_by_name->num_args); } /* Now loaded, so get it. */ - injection_callback = injection_point_cache_get(name, &private_data); - injection_callback(name, private_data); + cache_entry = injection_point_cache_get(name); + + /* Check the number of arguments with the cache. */ + if (cache_entry->num_args != num_args) + elog(ERROR, "incorrect number of arguments for injection point \"%s\": defined %d but expected %d", + name, cache_entry->num_args, num_args); + + if (cache_entry->num_args == 0) + cache_entry->data.callback(name, cache_entry->private_data); + else if (cache_entry->num_args == 1) + cache_entry->data.callback_1arg(name, cache_entry->private_data, arg1); + else + { + /* cannot be reached */ + Assert(false); + } #else elog(ERROR, "Injection points are not supported by this build"); #endif } + +/* + * Execute an injection point, with no arguments. + */ +void +InjectionPointRun(const char *name) +{ + InjectionPointRunInternal(name, 0, NULL); +} +/* 1-argument version */ +void +InjectionPointRun1Arg(const char *name, void *arg1) +{ + InjectionPointRunInternal(name, 1, arg1); +} diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out index dd9db06e10..740bdc8cfd 100644 --- a/src/test/modules/injection_points/expected/injection_points.out +++ b/src/test/modules/injection_points/expected/injection_points.out @@ -122,12 +122,43 @@ NOTICE: notice triggered for injection point TestInjectionLog2 (1 row) +-- 1-argument runs +SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error +ERROR: incorrect number of arguments for injection point "TestInjectionLog2": defined 0 but expected 1 +SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_run('TestInjectionLogArg1'); -- error +ERROR: incorrect number of arguments for injection point "TestInjectionLogArg1": defined 1 but expected 0 +SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice +NOTICE: notice triggered for injection point TestInjectionLogArg1: blah + injection_points_run_1arg +--------------------------- + +(1 row) + +SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice +NOTICE: notice triggered for injection point TestInjectionLogArg1: foobar + injection_points_run_1arg +--------------------------- + +(1 row) + SELECT injection_points_detach('TestInjectionLog2'); injection_points_detach ------------------------- (1 row) +SELECT injection_points_detach('TestInjectionLogArg1'); + injection_points_detach +------------------------- + +(1 row) + -- Runtime conditions SELECT injection_points_attach('TestConditionError', 'error'); injection_points_attach diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql index c16a33b08d..074b7b2ea7 100644 --- a/src/test/modules/injection_points/injection_points--1.0.sql +++ b/src/test/modules/injection_points/injection_points--1.0.sql @@ -45,6 +45,16 @@ RETURNS void AS 'MODULE_PATHNAME', 'injection_points_set_local' LANGUAGE C STRICT PARALLEL UNSAFE; +-- +-- injection_points_run_1arg() +-- +-- Executes the action attached to the injection point. +-- +CREATE FUNCTION injection_points_run_1arg(IN point_name TEXT, IN arg1 TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'injection_points_run_1arg' +LANGUAGE C STRICT PARALLEL UNSAFE; + -- -- injection_points_detach() -- diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index 5c44625d1d..7ef1c471a0 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -90,6 +90,9 @@ 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_notice_1arg(const char *name, + const void *private_data, + const void *arg1); extern PGDLLEXPORT void injection_wait(const char *name, const void *private_data); @@ -260,6 +263,19 @@ injection_wait(const char *name, const void *private_data) SpinLockRelease(&inj_state->lock); } +void +injection_notice_1arg(const char *name, const void *private_data, + const void *arg1) +{ + InjectionPointCondition *condition = (InjectionPointCondition *) private_data; + const char *str = (const char *) arg1; + + if (!injection_point_allowed(condition)) + return; + + elog(NOTICE, "notice triggered for injection point %s: %s", name, str); +} + /* * SQL function for creating an injection point. */ @@ -271,6 +287,7 @@ injection_points_attach(PG_FUNCTION_ARGS) char *action = text_to_cstring(PG_GETARG_TEXT_PP(1)); char *function; InjectionPointCondition condition = {0}; + int num_args = 0; if (strcmp(action, "error") == 0) function = "injection_error"; @@ -278,6 +295,11 @@ injection_points_attach(PG_FUNCTION_ARGS) function = "injection_notice"; else if (strcmp(action, "wait") == 0) function = "injection_wait"; + else if (strcmp(action, "notice_1arg") == 0) + { + function = "injection_notice_1arg"; + num_args = 1; + } else elog(ERROR, "incorrect action \"%s\" for injection point creation", action); @@ -288,7 +310,7 @@ injection_points_attach(PG_FUNCTION_ARGS) } InjectionPointAttach(name, "injection_points", function, &condition, - sizeof(InjectionPointCondition)); + sizeof(InjectionPointCondition), num_args); if (injection_point_local) { @@ -378,6 +400,21 @@ injection_points_set_local(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* + * SQL function for triggering an injection point, 1-argument flavor. + */ +PG_FUNCTION_INFO_V1(injection_points_run_1arg); +Datum +injection_points_run_1arg(PG_FUNCTION_ARGS) +{ + char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *arg1 = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + INJECTION_POINT_1ARG(name, (void *) arg1); + + PG_RETURN_VOID(); +} + /* * SQL function for dropping an injection point. */ diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql index 71e2972a7e..5b612a75c3 100644 --- a/src/test/modules/injection_points/sql/injection_points.sql +++ b/src/test/modules/injection_points/sql/injection_points.sql @@ -39,7 +39,16 @@ SELECT injection_points_run('TestInjectionLog2'); -- notice SELECT injection_points_detach('TestInjectionLog'); -- fails SELECT injection_points_run('TestInjectionLog2'); -- notice + +-- 1-argument runs +SELECT injection_points_run_1arg('TestInjectionLog2', 'blah'); -- error +SELECT injection_points_attach('TestInjectionLogArg1', 'notice_1arg'); +SELECT injection_points_run('TestInjectionLogArg1'); -- error +SELECT injection_points_run_1arg('TestInjectionLogArg1', 'blah'); -- notice +SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice + SELECT injection_points_detach('TestInjectionLog2'); +SELECT injection_points_detach('TestInjectionLogArg1'); -- Runtime conditions SELECT injection_points_attach('TestConditionError', 'error'); diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index a7c170476a..b74a2c1040 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3609,13 +3609,15 @@ uint32 WaitEventExtensionNew(const char *wait_event_name) macro: <programlisting> INJECTION_POINT(name); +INJECTION_POINT_1ARG(name, arg1); </programlisting> There are a few injection points already declared at strategic points within the server code. After adding a new injection point the code needs to be compiled in order for that injection point to be available in the binary. Add-ins written in C-language can declare injection points in - their own code using the same macro. + their own code using the same macro. It is possible to pass down arguments + to callbacks for runtime checks. </para> <para> @@ -3626,7 +3628,8 @@ extern void InjectionPointAttach(const char *name, const char *library, const char *function, const void *private_data, - int private_data_size); + int private_data_size, + int num_args); </programlisting> <literal>name</literal> is the name of the injection point, which when @@ -3634,6 +3637,8 @@ extern void InjectionPointAttach(const char *name, 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. + <literal>num_args</literal> is the number of arguments used by the + callback. </para> <para> -- 2.43.0
From 4dd79183e070069287c403c18a72768acf88f316 Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Mon, 20 May 2024 11:48:06 +0900 Subject: [PATCH 2/2] Support preloading of injection points This can be used to load an injection point in 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. --- src/include/utils/injection_point.h | 3 + src/backend/utils/misc/injection_point.c | 96 ++++++++++++++----- .../expected/injection_points.out | 32 +++++++ .../injection_points--1.0.sql | 10 ++ .../injection_points/injection_points.c | 14 +++ .../injection_points/sql/injection_points.sql | 7 ++ 6 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h index c2c0840706..9ef411cf29 100644 --- a/src/include/utils/injection_point.h +++ b/src/include/utils/injection_point.h @@ -15,9 +15,11 @@ * Injections points require --enable-injection-points. */ #ifdef USE_INJECTION_POINTS +#define INJECTION_POINT_PRELOAD(name) InjectionPointPreload(name) #define INJECTION_POINT(name) InjectionPointRun(name) #define INJECTION_POINT_1ARG(name, arg1) InjectionPointRun1Arg(name, arg1) #else +#define INJECTION_POINT_PRELOAD(name) ((void) name) #define INJECTION_POINT(name) ((void) name) #define INJECTION_POINT_1ARG(name) ((void) name, (void) arg1) #endif @@ -40,6 +42,7 @@ extern void InjectionPointAttach(const char *name, const void *private_data, int private_data_size, int num_args); +extern void InjectionPointPreload(const char *name); extern void InjectionPointRun(const char *name); extern void InjectionPointRun1Arg(const char *name, void *arg1); extern bool InjectionPointDetach(const char *name); diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c index 2bcdb2708c..e4d317a286 100644 --- a/src/backend/utils/misc/injection_point.c +++ b/src/backend/utils/misc/injection_point.c @@ -145,6 +145,37 @@ 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, + entry_by_name->num_args); +} + /* * injection_point_cache_get * @@ -292,6 +323,47 @@ InjectionPointDetach(const char *name) #endif } +/* + * Preload an injection point into the local cache. + * + * This is useful to be able to load a function pointer at a stage earlier + * than it would be run, especially if the injection point is called in a code + * path where memory allocations cannot happen, like critical sections. + */ +void +InjectionPointPreload(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); + LWLockRelease(InjectionPointLock); + + /* + * If not found, do nothing and remove it from the local cache if it + * existed there. + */ + if (!found) + { + injection_point_cache_remove(name); + return; + } + + /* Check first the local cache, and leave if this entry exists. */ + if (injection_point_cache_get(name) != NULL) + return; + + /* Nothing? Then load it and leave */ + injection_point_cache_load(entry_by_name); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + /* * Workhorse for execution of an injection point, if defined. * @@ -328,28 +400,8 @@ InjectionPointRunInternal(const char *name, int num_args, void *arg1) */ if (injection_point_cache_get(name) == NULL) { - char path[MAXPGPATH]; - void *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 = (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, name); - - /* add it to the local cache when found */ - injection_point_cache_add(name, injection_callback_local, - entry_by_name->private_data, - entry_by_name->num_args); + /* not found in local cache, so load and register it */ + injection_point_cache_load(entry_by_name); } /* Now loaded, so get it. */ diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out index 740bdc8cfd..392dd85234 100644 --- a/src/test/modules/injection_points/expected/injection_points.out +++ b/src/test/modules/injection_points/expected/injection_points.out @@ -159,6 +159,38 @@ SELECT injection_points_detach('TestInjectionLogArg1'); (1 row) +-- Preloading +SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing + injection_points_preload +-------------------------- + +(1 row) + +SELECT injection_points_attach('TestInjectionLogPreload', 'notice'); + injection_points_attach +------------------------- + +(1 row) + +SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing happens + injection_points_preload +-------------------------- + +(1 row) + +SELECT injection_points_run('TestInjectionLogPreload'); -- runs from cache +NOTICE: notice triggered for injection point TestInjectionLogPreload + injection_points_run +---------------------- + +(1 row) + +SELECT injection_points_detach('TestInjectionLogPreload'); + injection_points_detach +------------------------- + +(1 row) + -- Runtime conditions SELECT injection_points_attach('TestConditionError', 'error'); injection_points_attach diff --git a/src/test/modules/injection_points/injection_points--1.0.sql b/src/test/modules/injection_points/injection_points--1.0.sql index 074b7b2ea7..29d43cc556 100644 --- a/src/test/modules/injection_points/injection_points--1.0.sql +++ b/src/test/modules/injection_points/injection_points--1.0.sql @@ -14,6 +14,16 @@ RETURNS void AS 'MODULE_PATHNAME', 'injection_points_attach' LANGUAGE C STRICT PARALLEL UNSAFE; +-- +-- injection_points_preload() +-- +-- Preload an injection point already attached. +-- +CREATE FUNCTION injection_points_preload(IN point_name TEXT) +RETURNS void +AS 'MODULE_PATHNAME', 'injection_points_preload' +LANGUAGE C STRICT PARALLEL UNSAFE; + -- -- injection_points_run() -- diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index 7ef1c471a0..086b8a6a5e 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -324,6 +324,20 @@ injection_points_attach(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } +/* + * SQL function for preloading an injection point. + */ +PG_FUNCTION_INFO_V1(injection_points_preload); +Datum +injection_points_preload(PG_FUNCTION_ARGS) +{ + char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + INJECTION_POINT_PRELOAD(name); + + PG_RETURN_VOID(); +} + /* * SQL function for triggering an injection point. */ diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql index 5b612a75c3..69822be647 100644 --- a/src/test/modules/injection_points/sql/injection_points.sql +++ b/src/test/modules/injection_points/sql/injection_points.sql @@ -50,6 +50,13 @@ SELECT injection_points_run_1arg('TestInjectionLogArg1', 'foobar'); -- notice SELECT injection_points_detach('TestInjectionLog2'); SELECT injection_points_detach('TestInjectionLogArg1'); +-- Preloading +SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing +SELECT injection_points_attach('TestInjectionLogPreload', 'notice'); +SELECT injection_points_preload('TestInjectionLogPreload'); -- nothing happens +SELECT injection_points_run('TestInjectionLogPreload'); -- runs from cache +SELECT injection_points_detach('TestInjectionLogPreload'); + -- Runtime conditions SELECT injection_points_attach('TestConditionError', 'error'); -- Any follow-up injection point attached will be local to this process. -- 2.43.0
signature.asc
Description: PGP signature