Hi all, As mentioned during my talk about injection points at the last pgconf.dev, I think that we should be more aggressive with the backpatching of the core APIs of this facility, to ease the porting of tests across more branches and the maintenance of this stuff itself, so as it evolves in a consistent way across all stable branches.
Backporting that down to v16 would require more work, like in the area of custom wait events for the injection point category for isolation tests. The story is different for v17, where most of the basics are in place. Please see the attached, that adds the following parts to REL_17_STABLE: - Cached points and loading, for critical sections. - IS_INJECTION_POINT_ATTACHED(), for stack manipulations. - Runtime arguments. Thoughts or comments? -- Michael
From d5d3f0bb997a5698ffdb0fa89131d1683460a15c Mon Sep 17 00:00:00 2001 From: Michael Paquier <mich...@paquier.xyz> Date: Fri, 27 Jun 2025 18:23:45 +0900 Subject: [PATCH] Make injection point facilities consistent with HEAD This brings the set of features that exist for injection points on par with HEAD, for branch REL_17_STABLE. --- src/include/utils/injection_point.h | 20 +++-- src/backend/access/gin/ginbtree.c | 6 +- src/backend/access/heap/heapam.c | 2 +- src/backend/access/index/genam.c | 2 +- src/backend/access/transam/xlog.c | 4 +- src/backend/commands/indexcmds.c | 4 +- src/backend/commands/vacuum.c | 12 +-- src/backend/replication/logical/logical.c | 2 +- src/backend/tcop/postgres.c | 6 +- src/backend/utils/cache/catcache.c | 2 +- src/backend/utils/cache/inval.c | 2 +- src/backend/utils/misc/injection_point.c | 59 ++++++++++-- .../expected/injection_points.out | 90 +++++++++++++++++++ .../injection_points--1.0.sql | 26 +++++- .../injection_points/injection_points.c | 81 ++++++++++++++--- .../injection_points/sql/injection_points.sql | 19 ++++ doc/src/sgml/xfunc.sgml | 54 ++++++++++- 17 files changed, 344 insertions(+), 47 deletions(-) diff --git a/src/include/utils/injection_point.h b/src/include/utils/injection_point.h index 6e417cedc60b..1a7c30677054 100644 --- a/src/include/utils/injection_point.h +++ b/src/include/utils/injection_point.h @@ -12,19 +12,26 @@ #define INJECTION_POINT_H /* - * Injections points require --enable-injection-points. + * Injection points require --enable-injection-points. */ #ifdef USE_INJECTION_POINTS -#define INJECTION_POINT(name) InjectionPointRun(name) +#define INJECTION_POINT_LOAD(name) InjectionPointLoad(name) +#define INJECTION_POINT(name, arg) InjectionPointRun(name, arg) +#define INJECTION_POINT_CACHED(name, arg) InjectionPointCached(name, arg) +#define IS_INJECTION_POINT_ATTACHED(name) IsInjectionPointAttached(name) #else -#define INJECTION_POINT(name) ((void) name) +#define INJECTION_POINT_LOAD(name) ((void) name) +#define INJECTION_POINT(name, arg) ((void) name) +#define INJECTION_POINT_CACHED(name, arg) ((void) name) +#define IS_INJECTION_POINT_ATTACHED(name) (false) #endif /* * Typedef for callback function launched by an injection point. */ typedef void (*InjectionPointCallback) (const char *name, - const void *private_data); + const void *private_data, + void *arg); extern Size InjectionPointShmemSize(void); extern void InjectionPointShmemInit(void); @@ -34,7 +41,10 @@ extern void InjectionPointAttach(const char *name, const char *function, const void *private_data, int private_data_size); -extern void InjectionPointRun(const char *name); +extern void InjectionPointLoad(const char *name); +extern void InjectionPointRun(const char *name, void *arg); +extern void InjectionPointCached(const char *name, void *arg); +extern bool IsInjectionPointAttached(const char *name); extern bool InjectionPointDetach(const char *name); #ifdef EXEC_BACKEND diff --git a/src/backend/access/gin/ginbtree.c b/src/backend/access/gin/ginbtree.c index b7a5013896ad..35d91f1e2387 100644 --- a/src/backend/access/gin/ginbtree.c +++ b/src/backend/access/gin/ginbtree.c @@ -685,9 +685,9 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack, #ifdef USE_INJECTION_POINTS if (GinPageIsLeaf(BufferGetPage(stack->buffer))) - INJECTION_POINT("gin-leave-leaf-split-incomplete"); + INJECTION_POINT("gin-leave-leaf-split-incomplete", NULL); else - INJECTION_POINT("gin-leave-internal-split-incomplete"); + INJECTION_POINT("gin-leave-internal-split-incomplete", NULL); #endif /* search parent to lock */ @@ -778,7 +778,7 @@ ginFinishSplit(GinBtree btree, GinBtreeStack *stack, bool freestack, static void ginFinishOldSplit(GinBtree btree, GinBtreeStack *stack, GinStatsData *buildStats, int access) { - INJECTION_POINT("gin-finish-incomplete-split"); + INJECTION_POINT("gin-finish-incomplete-split", NULL); elog(DEBUG1, "finishing incomplete split of block %u in gin index \"%s\"", stack->blkno, RelationGetRelationName(btree->index)); diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index cce38f482bd6..01a6ec75d6f5 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -3294,7 +3294,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, interesting_attrs = bms_add_members(interesting_attrs, id_attrs); block = ItemPointerGetBlockNumber(otid); - INJECTION_POINT("heap_update-before-pin"); + INJECTION_POINT("heap_update-before-pin", NULL); buffer = ReadBuffer(relation, block); page = BufferGetPage(buffer); diff --git a/src/backend/access/index/genam.c b/src/backend/access/index/genam.c index b123acc5a609..6c75ab7b304b 100644 --- a/src/backend/access/index/genam.c +++ b/src/backend/access/index/genam.c @@ -841,7 +841,7 @@ systable_inplace_update_begin(Relation relation, elog(ERROR, "giving up after too many tries to overwrite row"); memcpy(mutable_key, key, sizeof(ScanKeyData) * nkeys); - INJECTION_POINT("inplace-before-pin"); + INJECTION_POINT("inplace-before-pin", NULL); scan = systable_beginscan(relation, indexId, indexOK, snapshot, nkeys, mutable_key); oldtup = systable_getnext(scan); diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index a00786d40c80..ff5ed4c113b0 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7314,7 +7314,7 @@ CreateCheckPoint(int flags) UpdateCheckPointDistanceEstimate(RedoRecPtr - PriorRedoPtr); #ifdef USE_INJECTION_POINTS - INJECTION_POINT("checkpoint-before-old-wal-removal"); + INJECTION_POINT("checkpoint-before-old-wal-removal", NULL); #endif /* @@ -7717,7 +7717,7 @@ CreateRestartPoint(int flags) * This location needs to be after CheckPointGuts() to ensure that some * work has already happened during this checkpoint. */ - INJECTION_POINT("create-restart-point"); + INJECTION_POINT("create-restart-point", NULL); /* * Remember the prior checkpoint's redo ptr for diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 75edb3697b30..3fae544c11a1 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -3795,9 +3795,9 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein #ifdef USE_INJECTION_POINTS if (idx->safe) - INJECTION_POINT("reindex-conc-index-safe"); + INJECTION_POINT("reindex-conc-index-safe", NULL); else - INJECTION_POINT("reindex-conc-index-not-safe"); + INJECTION_POINT("reindex-conc-index-not-safe", NULL); #endif idx->tableId = RelationGetRelid(heapRel); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index a2132ecedaf8..132cad6c7a06 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -2173,11 +2173,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, #ifdef USE_INJECTION_POINTS if (params->index_cleanup == VACOPTVALUE_AUTO) - INJECTION_POINT("vacuum-index-cleanup-auto"); + INJECTION_POINT("vacuum-index-cleanup-auto", NULL); else if (params->index_cleanup == VACOPTVALUE_DISABLED) - INJECTION_POINT("vacuum-index-cleanup-disabled"); + INJECTION_POINT("vacuum-index-cleanup-disabled", NULL); else if (params->index_cleanup == VACOPTVALUE_ENABLED) - INJECTION_POINT("vacuum-index-cleanup-enabled"); + INJECTION_POINT("vacuum-index-cleanup-enabled", NULL); #endif /* @@ -2195,11 +2195,11 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params, #ifdef USE_INJECTION_POINTS if (params->truncate == VACOPTVALUE_AUTO) - INJECTION_POINT("vacuum-truncate-auto"); + INJECTION_POINT("vacuum-truncate-auto", NULL); else if (params->truncate == VACOPTVALUE_DISABLED) - INJECTION_POINT("vacuum-truncate-disabled"); + INJECTION_POINT("vacuum-truncate-disabled", NULL); else if (params->truncate == VACOPTVALUE_ENABLED) - INJECTION_POINT("vacuum-truncate-enabled"); + INJECTION_POINT("vacuum-truncate-enabled", NULL); #endif /* diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index 206fb932484c..d07fcd089b9d 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -1923,7 +1923,7 @@ LogicalConfirmReceivedLocation(XLogRecPtr lsn) /* trigger injection point, but only if segment changes */ if (seg1 != seg2) - INJECTION_POINT("logical-replication-slot-advance-segment"); + INJECTION_POINT("logical-replication-slot-advance-segment", NULL); #endif ReplicationSlotMarkDirty(); diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 9cd1d0abe35f..a4e56a02c0ea 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -3442,7 +3442,7 @@ ProcessInterrupts(void) IdleInTransactionSessionTimeoutPending = false; if (IdleInTransactionSessionTimeout > 0) { - INJECTION_POINT("idle-in-transaction-session-timeout"); + INJECTION_POINT("idle-in-transaction-session-timeout", NULL); ereport(FATAL, (errcode(ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT), errmsg("terminating connection due to idle-in-transaction timeout"))); @@ -3455,7 +3455,7 @@ ProcessInterrupts(void) TransactionTimeoutPending = false; if (TransactionTimeout > 0) { - INJECTION_POINT("transaction-timeout"); + INJECTION_POINT("transaction-timeout", NULL); ereport(FATAL, (errcode(ERRCODE_TRANSACTION_TIMEOUT), errmsg("terminating connection due to transaction timeout"))); @@ -3468,7 +3468,7 @@ ProcessInterrupts(void) IdleSessionTimeoutPending = false; if (IdleSessionTimeout > 0) { - INJECTION_POINT("idle-session-timeout"); + INJECTION_POINT("idle-session-timeout", NULL); ereport(FATAL, (errcode(ERRCODE_IDLE_SESSION_TIMEOUT), errmsg("terminating connection due to idle-session timeout"))); diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c index 59d625b244c7..900da8b3bf1b 100644 --- a/src/backend/utils/cache/catcache.c +++ b/src/backend/utils/cache/catcache.c @@ -1906,7 +1906,7 @@ SearchCatCacheList(CatCache *cache, /* Injection point to help testing the recursive invalidation case */ if (first_iter) { - INJECTION_POINT("catcache-list-miss-systable-scan-started"); + INJECTION_POINT("catcache-list-miss-systable-scan-started", NULL); first_iter = false; } diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c index 66e04f973f67..77473b8d9e7f 100644 --- a/src/backend/utils/cache/inval.c +++ b/src/backend/utils/cache/inval.c @@ -1032,7 +1032,7 @@ AtEOXact_Inval(bool isCommit) /* Must be at top of stack */ Assert(transInvalInfo->my_level == 1 && transInvalInfo->parent == NULL); - INJECTION_POINT("transaction-end-process-inval"); + INJECTION_POINT("transaction-end-process-inval", NULL); if (isCommit) { diff --git a/src/backend/utils/misc/injection_point.c b/src/backend/utils/misc/injection_point.c index b33cddefabc3..c80653a74589 100644 --- a/src/backend/utils/misc/injection_point.c +++ b/src/backend/utils/misc/injection_point.c @@ -17,6 +17,10 @@ */ #include "postgres.h" +#include "utils/injection_point.h" + +#ifdef USE_INJECTION_POINTS + #include <sys/stat.h> #include "fmgr.h" @@ -25,11 +29,8 @@ #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/hsearch.h" -#include "utils/injection_point.h" #include "utils/memutils.h" -#ifdef USE_INJECTION_POINTS - /* Field sizes */ #define INJ_NAME_MAXLEN 64 #define INJ_LIB_MAXLEN 128 @@ -519,19 +520,67 @@ InjectionPointCacheRefresh(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 + InjectionPointCacheRefresh(name); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + /* * Execute an injection point, if defined. */ void -InjectionPointRun(const char *name) +InjectionPointRun(const char *name, void *arg) { #ifdef USE_INJECTION_POINTS InjectionPointCacheEntry *cache_entry; cache_entry = InjectionPointCacheRefresh(name); if (cache_entry) - cache_entry->callback(name, cache_entry->private_data); + cache_entry->callback(name, cache_entry->private_data, arg); #else 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, void *arg) +{ +#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, arg); +#else + elog(ERROR, "Injection points are not supported by this build"); +#endif +} + +/* + * Test if an injection point is defined. + */ +bool +IsInjectionPointAttached(const char *name) +{ +#ifdef USE_INJECTION_POINTS + return InjectionPointCacheRefresh(name) != NULL; +#else + elog(ERROR, "Injection points are not supported by this build"); + return false; /* silence compiler */ +#endif +} diff --git a/src/test/modules/injection_points/expected/injection_points.out b/src/test/modules/injection_points/expected/injection_points.out index dd9db06e10bd..43bcdd01582f 100644 --- a/src/test/modules/injection_points/expected/injection_points.out +++ b/src/test/modules/injection_points/expected/injection_points.out @@ -6,6 +6,19 @@ CREATE FUNCTION wait_pid(int) RETURNS void AS :'regresslib' LANGUAGE C STRICT; +-- Non-strict checks +SELECT injection_points_run(NULL); + injection_points_run +---------------------- + +(1 row) + +SELECT injection_points_cached(NULL); + injection_points_cached +------------------------- + +(1 row) + SELECT injection_points_attach('TestInjectionBooh', 'booh'); ERROR: incorrect action "booh" for injection point creation SELECT injection_points_attach('TestInjectionError', 'error'); @@ -39,6 +52,20 @@ NOTICE: notice triggered for injection point TestInjectionLog2 (1 row) +SELECT injection_points_run('TestInjectionLog2', NULL); -- notice +NOTICE: notice triggered for injection point TestInjectionLog2 + injection_points_run +---------------------- + +(1 row) + +SELECT injection_points_run('TestInjectionLog2', 'foobar'); -- notice + arg +NOTICE: notice triggered for injection point TestInjectionLog2 (foobar) + injection_points_run +---------------------- + +(1 row) + SELECT injection_points_run('TestInjectionLog'); -- notice NOTICE: notice triggered for injection point TestInjectionLog injection_points_run @@ -48,6 +75,10 @@ NOTICE: notice triggered for injection point TestInjectionLog SELECT injection_points_run('TestInjectionError'); -- error ERROR: error triggered for injection point TestInjectionError +SELECT injection_points_run('TestInjectionError', NULL); -- error +ERROR: error triggered for injection point TestInjectionError +SELECT injection_points_run('TestInjectionError', 'foobar2'); -- error + arg +ERROR: error triggered for injection point TestInjectionError (foobar2) -- Re-load cache and run again. \c SELECT injection_points_run('TestInjectionLog2'); -- notice @@ -128,6 +159,65 @@ 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 +----------------------- + +(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_cached('TestInjectionLogLoad'); -- runs from cache +NOTICE: notice triggered for injection point TestInjectionLogLoad + injection_points_cached +------------------------- + +(1 row) + +SELECT injection_points_cached('TestInjectionLogLoad', NULL); -- runs from cache +NOTICE: notice triggered for injection point TestInjectionLogLoad + injection_points_cached +------------------------- + +(1 row) + +SELECT injection_points_cached('TestInjectionLogLoad', 'foobar'); -- runs from cache +NOTICE: notice triggered for injection point TestInjectionLogLoad (foobar) + injection_points_cached +------------------------- + +(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 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 519641e6d041..a737683a9b2c 100644 --- a/src/test/modules/injection_points/injection_points--1.0.sql +++ b/src/test/modules/injection_points/injection_points--1.0.sql @@ -14,15 +14,37 @@ RETURNS void AS 'MODULE_PATHNAME', 'injection_points_attach' 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() -- -- Executes the action attached to the injection point. -- -CREATE FUNCTION injection_points_run(IN point_name TEXT) +CREATE FUNCTION injection_points_run(IN point_name TEXT, + IN arg TEXT DEFAULT NULL) RETURNS void AS 'MODULE_PATHNAME', 'injection_points_run' -LANGUAGE C STRICT PARALLEL UNSAFE; +LANGUAGE C 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, + IN arg TEXT DEFAULT NULL) +RETURNS void +AS 'MODULE_PATHNAME', 'injection_points_cached' +LANGUAGE C PARALLEL UNSAFE; -- -- injection_points_wakeup() diff --git a/src/test/modules/injection_points/injection_points.c b/src/test/modules/injection_points/injection_points.c index 1b695a182032..b2048e310e25 100644 --- a/src/test/modules/injection_points/injection_points.c +++ b/src/test/modules/injection_points/injection_points.c @@ -87,11 +87,14 @@ typedef struct InjectionPointSharedState static InjectionPointSharedState *inj_state = NULL; extern PGDLLEXPORT void injection_error(const char *name, - const void *private_data); + const void *private_data, + void *arg); extern PGDLLEXPORT void injection_notice(const char *name, - const void *private_data); + const void *private_data, + void *arg); extern PGDLLEXPORT void injection_wait(const char *name, - const void *private_data); + const void *private_data, + void *arg); /* track if injection points attached in this process are linked to it */ static bool injection_point_local = false; @@ -175,30 +178,40 @@ injection_points_cleanup(int code, Datum arg) /* Set of callbacks available to be attached to an injection point. */ void -injection_error(const char *name, const void *private_data) +injection_error(const char *name, const void *private_data, void *arg) { InjectionPointCondition *condition = (InjectionPointCondition *) private_data; + char *argstr = (char *) arg; if (!injection_point_allowed(condition)) return; - elog(ERROR, "error triggered for injection point %s", name); + if (argstr) + elog(ERROR, "error triggered for injection point %s (%s)", + name, argstr); + else + elog(ERROR, "error triggered for injection point %s", name); } void -injection_notice(const char *name, const void *private_data) +injection_notice(const char *name, const void *private_data, void *arg) { InjectionPointCondition *condition = (InjectionPointCondition *) private_data; + char *argstr = (char *) arg; if (!injection_point_allowed(condition)) return; - elog(NOTICE, "notice triggered for injection point %s", name); + if (argstr) + elog(NOTICE, "notice triggered for injection point %s (%s)", + name, argstr); + else + elog(NOTICE, "notice triggered for injection point %s", name); } /* Wait on a condition variable, awaken by injection_points_wakeup() */ void -injection_wait(const char *name, const void *private_data) +injection_wait(const char *name, const void *private_data, void *arg) { uint32 old_wait_counts = 0; int index = -1; @@ -299,6 +312,24 @@ injection_points_attach(PG_FUNCTION_ARGS) inj_list_local = lappend(inj_list_local, makeString(pstrdup(name))); MemoryContextSwitchTo(oldctx); } + + 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(); } @@ -309,9 +340,39 @@ PG_FUNCTION_INFO_V1(injection_points_run); Datum injection_points_run(PG_FUNCTION_ARGS) { - char *name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + char *name; + char *arg = NULL; - INJECTION_POINT(name); + if (PG_ARGISNULL(0)) + PG_RETURN_VOID(); + name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (!PG_ARGISNULL(1)) + arg = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + INJECTION_POINT(name, arg); + + 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; + char *arg = NULL; + + if (PG_ARGISNULL(0)) + PG_RETURN_VOID(); + name = text_to_cstring(PG_GETARG_TEXT_PP(0)); + + if (!PG_ARGISNULL(1)) + arg = text_to_cstring(PG_GETARG_TEXT_PP(1)); + + INJECTION_POINT_CACHED(name, arg); PG_RETURN_VOID(); } diff --git a/src/test/modules/injection_points/sql/injection_points.sql b/src/test/modules/injection_points/sql/injection_points.sql index 71e2972a7e49..d9748331c771 100644 --- a/src/test/modules/injection_points/sql/injection_points.sql +++ b/src/test/modules/injection_points/sql/injection_points.sql @@ -9,6 +9,10 @@ CREATE FUNCTION wait_pid(int) AS :'regresslib' LANGUAGE C STRICT; +-- Non-strict checks +SELECT injection_points_run(NULL); +SELECT injection_points_cached(NULL); + SELECT injection_points_attach('TestInjectionBooh', 'booh'); SELECT injection_points_attach('TestInjectionError', 'error'); SELECT injection_points_attach('TestInjectionLog', 'notice'); @@ -16,8 +20,12 @@ SELECT injection_points_attach('TestInjectionLog2', 'notice'); SELECT injection_points_run('TestInjectionBooh'); -- nothing SELECT injection_points_run('TestInjectionLog2'); -- notice +SELECT injection_points_run('TestInjectionLog2', NULL); -- notice +SELECT injection_points_run('TestInjectionLog2', 'foobar'); -- notice + arg SELECT injection_points_run('TestInjectionLog'); -- notice SELECT injection_points_run('TestInjectionError'); -- error +SELECT injection_points_run('TestInjectionError', NULL); -- error +SELECT injection_points_run('TestInjectionError', 'foobar2'); -- error + arg -- Re-load cache and run again. \c @@ -41,6 +49,17 @@ SELECT injection_points_detach('TestInjectionLog'); -- fails 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_cached('TestInjectionLogLoad', NULL); -- runs from cache +SELECT injection_points_cached('TestInjectionLogLoad', 'foobar'); -- runs from cache +SELECT injection_points_run('TestInjectionLogLoad'); -- runs from cache +SELECT injection_points_detach('TestInjectionLogLoad'); + -- Runtime conditions SELECT injection_points_attach('TestConditionError', 'error'); -- Any follow-up injection point attached will be local to this process. diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index a8aa871918a7..7f42b25ddbe9 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3608,15 +3608,34 @@ uint32 WaitEventExtensionNew(const char *wait_event_name) An injection point with a given <literal>name</literal> is declared using macro: <programlisting> -INJECTION_POINT(name); +INJECTION_POINT(name, arg); </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. The injection point names should - use lower-case characters, with terms separated by dashes. + their own code using the same macro. The injection point names should use + lower-case characters, with terms separated by + dashes. <literal>arg</literal> is an optional argument value given to the + callback at run-time. + </para> + + <para> + 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, arg); +</programlisting> + + 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> @@ -3642,7 +3661,9 @@ extern void InjectionPointAttach(const char *name, <literal>InjectionPointCallback</literal>: <programlisting> static void -custom_injection_callback(const char *name, const void *private_data) +custom_injection_callback(const char *name, + const void *private_data, + void *arg) { uint32 wait_event_info = WaitEventInjectionPointNew(name); @@ -3656,6 +3677,31 @@ custom_injection_callback(const char *name, const void *private_data) logic. </para> + <para> + An alternative way to define the action to take when an injection point + is reached is to add the testing code alongside the normal source + code. This can be useful if the action e.g. depends on local variables + that are not accessible to loaded modules. The + <function>IS_INJECTION_POINT_ATTACHED</function> macro can then be used + to check if an injection point is attached, for example: +<programlisting> +#ifdef USE_INJECTION_POINTS +if (IS_INJECTION_POINT_ATTACHED("before-foobar")) +{ + /* change a local variable if injection point is attached */ + local_var = 123; + + /* also execute the callback */ + INJECTION_POINT_CACHED("before-foobar", NULL); +} +#endif +</programlisting> + Note that the callback attached to the injection point will not be + executed by the <function>IS_INJECTION_POINT_ATTACHED</function> + macro. If you want to execute the callback, you must also call + <function>INJECTION_POINT_CACHED</function> like in the above example. + </para> + <para> Optionally, it is possible to detach an injection point by calling: <programlisting> -- 2.50.0
signature.asc
Description: PGP signature