Skip to content

Commit f587338

Browse files
committed
injection_points: Introduce runtime conditions
This adds a new SQL function injection_points_set_local() that can be used to force injection points to be run only in the process where they are attached. This is handy for SQL tests to: - Detach automatically injection points when the process exits. - Allow tests with injection points to run concurrently with other test suites, so as such modules do not have to be marked with NO_INSTALLCHECK. Currently, the only condition that can be registered is for a PID. This could be extended to more kinds later, if required, like database names/OIDs, roles, or more concepts I did not consider. Using a single function for SQL scripts is an idea from Heikki Linnakangas. Reviewed-by: Andrey Borodin Discussion: https://postgr.es/m/ZfP7IDs9TvrKe49x@paquier.xyz
1 parent 705843d commit f587338

File tree

5 files changed

+285
-0
lines changed

5 files changed

+285
-0
lines changed

src/test/modules/injection_points/expected/injection_points.out

+77
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,81 @@ NOTICE: notice triggered for injection point TestInjectionLog2
115115

116116
(1 row)
117117

118+
SELECT injection_points_detach('TestInjectionLog2');
119+
injection_points_detach
120+
-------------------------
121+
122+
(1 row)
123+
124+
-- Runtime conditions
125+
SELECT injection_points_attach('TestConditionError', 'error');
126+
injection_points_attach
127+
-------------------------
128+
129+
(1 row)
130+
131+
-- Any follow-up injection point attached will be local to this process.
132+
SELECT injection_points_set_local();
133+
injection_points_set_local
134+
----------------------------
135+
136+
(1 row)
137+
138+
SELECT injection_points_attach('TestConditionLocal1', 'error');
139+
injection_points_attach
140+
-------------------------
141+
142+
(1 row)
143+
144+
SELECT injection_points_attach('TestConditionLocal2', 'notice');
145+
injection_points_attach
146+
-------------------------
147+
148+
(1 row)
149+
150+
SELECT injection_points_run('TestConditionLocal1'); -- error
151+
ERROR: error triggered for injection point TestConditionLocal1
152+
SELECT injection_points_run('TestConditionLocal2'); -- notice
153+
NOTICE: notice triggered for injection point TestConditionLocal2
154+
injection_points_run
155+
----------------------
156+
157+
(1 row)
158+
159+
-- reload, local injection points should be gone.
160+
\c
161+
SELECT injection_points_run('TestConditionLocal1'); -- nothing
162+
injection_points_run
163+
----------------------
164+
165+
(1 row)
166+
167+
SELECT injection_points_run('TestConditionLocal2'); -- nothing
168+
injection_points_run
169+
----------------------
170+
171+
(1 row)
172+
173+
SELECT injection_points_run('TestConditionError'); -- error
174+
ERROR: error triggered for injection point TestConditionError
175+
SELECT injection_points_detach('TestConditionError');
176+
injection_points_detach
177+
-------------------------
178+
179+
(1 row)
180+
181+
-- Attaching injection points that use the same name as one defined locally
182+
-- previously should work.
183+
SELECT injection_points_attach('TestConditionLocal1', 'error');
184+
injection_points_attach
185+
-------------------------
186+
187+
(1 row)
188+
189+
SELECT injection_points_detach('TestConditionLocal1');
190+
injection_points_detach
191+
-------------------------
192+
193+
(1 row)
194+
118195
DROP EXTENSION injection_points;

src/test/modules/injection_points/injection_points--1.0.sql

+11
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ RETURNS void
3434
AS 'MODULE_PATHNAME', 'injection_points_wakeup'
3535
LANGUAGE C STRICT PARALLEL UNSAFE;
3636

37+
--
38+
-- injection_points_set_local()
39+
--
40+
-- Trigger switch to link any future injection points attached to the
41+
-- current process, useful to make SQL tests concurrently-safe.
42+
--
43+
CREATE FUNCTION injection_points_set_local()
44+
RETURNS void
45+
AS 'MODULE_PATHNAME', 'injection_points_set_local'
46+
LANGUAGE C STRICT PARALLEL UNSAFE;
47+
3748
--
3849
-- injection_points_detach()
3950
--

src/test/modules/injection_points/injection_points.c

+176
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
#include "postgres.h"
1919

2020
#include "fmgr.h"
21+
#include "miscadmin.h"
2122
#include "storage/condition_variable.h"
2223
#include "storage/dsm_registry.h"
24+
#include "storage/ipc.h"
2325
#include "storage/lwlock.h"
2426
#include "storage/shmem.h"
2527
#include "utils/builtins.h"
@@ -31,6 +33,23 @@ PG_MODULE_MAGIC;
3133
/* Maximum number of waits usable in injection points at once */
3234
#define INJ_MAX_WAIT 8
3335
#define INJ_NAME_MAXLEN 64
36+
#define INJ_MAX_CONDITION 4
37+
38+
/*
39+
* Conditions related to injection points. This tracks in shared memory the
40+
* runtime conditions under which an injection point is allowed to run.
41+
*
42+
* If more types of runtime conditions need to be tracked, this structure
43+
* should be expanded.
44+
*/
45+
typedef struct InjectionPointCondition
46+
{
47+
/* Name of the injection point related to this condition */
48+
char name[INJ_NAME_MAXLEN];
49+
50+
/* ID of the process where the injection point is allowed to run */
51+
int pid;
52+
} InjectionPointCondition;
3453

3554
/* Shared state information for injection points. */
3655
typedef struct InjectionPointSharedState
@@ -46,6 +65,9 @@ typedef struct InjectionPointSharedState
4665

4766
/* Condition variable used for waits and wakeups */
4867
ConditionVariable wait_point;
68+
69+
/* Conditions to run an injection point */
70+
InjectionPointCondition conditions[INJ_MAX_CONDITION];
4971
} InjectionPointSharedState;
5072

5173
/* Pointer to shared-memory state. */
@@ -55,6 +77,8 @@ extern PGDLLEXPORT void injection_error(const char *name);
5577
extern PGDLLEXPORT void injection_notice(const char *name);
5678
extern PGDLLEXPORT void injection_wait(const char *name);
5779

80+
/* track if injection points attached in this process are linked to it */
81+
static bool injection_point_local = false;
5882

5983
/*
6084
* Callback for shared memory area initialization.
@@ -67,6 +91,7 @@ injection_point_init_state(void *ptr)
6791
SpinLockInit(&state->lock);
6892
memset(state->wait_counts, 0, sizeof(state->wait_counts));
6993
memset(state->name, 0, sizeof(state->name));
94+
memset(state->conditions, 0, sizeof(state->conditions));
7095
ConditionVariableInit(&state->wait_point);
7196
}
7297

@@ -87,16 +112,92 @@ injection_init_shmem(void)
87112
&found);
88113
}
89114

115+
/*
116+
* Check runtime conditions associated to an injection point.
117+
*
118+
* Returns true if the named injection point is allowed to run, and false
119+
* otherwise. Multiple conditions can be associated to a single injection
120+
* point, so check them all.
121+
*/
122+
static bool
123+
injection_point_allowed(const char *name)
124+
{
125+
bool result = true;
126+
127+
if (inj_state == NULL)
128+
injection_init_shmem();
129+
130+
SpinLockAcquire(&inj_state->lock);
131+
132+
for (int i = 0; i < INJ_MAX_CONDITION; i++)
133+
{
134+
InjectionPointCondition *condition = &inj_state->conditions[i];
135+
136+
if (strcmp(condition->name, name) == 0)
137+
{
138+
/*
139+
* Check if this injection point is allowed to run in this
140+
* process.
141+
*/
142+
if (MyProcPid != condition->pid)
143+
{
144+
result = false;
145+
break;
146+
}
147+
}
148+
}
149+
150+
SpinLockRelease(&inj_state->lock);
151+
152+
return result;
153+
}
154+
155+
/*
156+
* before_shmem_exit callback to remove injection points linked to a
157+
* specific process.
158+
*/
159+
static void
160+
injection_points_cleanup(int code, Datum arg)
161+
{
162+
/* Leave if nothing is tracked locally */
163+
if (!injection_point_local)
164+
return;
165+
166+
SpinLockAcquire(&inj_state->lock);
167+
for (int i = 0; i < INJ_MAX_CONDITION; i++)
168+
{
169+
InjectionPointCondition *condition = &inj_state->conditions[i];
170+
171+
if (condition->name[0] == '\0')
172+
continue;
173+
174+
if (condition->pid != MyProcPid)
175+
continue;
176+
177+
/* Detach the injection point and unregister condition */
178+
InjectionPointDetach(condition->name);
179+
condition->name[0] = '\0';
180+
condition->pid = 0;
181+
}
182+
SpinLockRelease(&inj_state->lock);
183+
}
184+
90185
/* Set of callbacks available to be attached to an injection point. */
91186
void
92187
injection_error(const char *name)
93188
{
189+
if (!injection_point_allowed(name))
190+
return;
191+
94192
elog(ERROR, "error triggered for injection point %s", name);
95193
}
96194

97195
void
98196
injection_notice(const char *name)
99197
{
198+
if (!injection_point_allowed(name))
199+
return;
200+
100201
elog(NOTICE, "notice triggered for injection point %s", name);
101202
}
102203

@@ -111,6 +212,9 @@ injection_wait(const char *name)
111212
if (inj_state == NULL)
112213
injection_init_shmem();
113214

215+
if (!injection_point_allowed(name))
216+
return;
217+
114218
/*
115219
* Use the injection point name for this custom wait event. Note that
116220
* this custom wait event name is not released, but we don't care much for
@@ -182,6 +286,35 @@ injection_points_attach(PG_FUNCTION_ARGS)
182286

183287
InjectionPointAttach(name, "injection_points", function);
184288

289+
if (injection_point_local)
290+
{
291+
int index = -1;
292+
293+
/*
294+
* Register runtime condition to link this injection point to the
295+
* current process.
296+
*/
297+
SpinLockAcquire(&inj_state->lock);
298+
for (int i = 0; i < INJ_MAX_CONDITION; i++)
299+
{
300+
InjectionPointCondition *condition = &inj_state->conditions[i];
301+
302+
if (condition->name[0] == '\0')
303+
{
304+
index = i;
305+
strlcpy(condition->name, name, INJ_NAME_MAXLEN);
306+
condition->pid = MyProcPid;
307+
break;
308+
}
309+
}
310+
SpinLockRelease(&inj_state->lock);
311+
312+
if (index < 0)
313+
elog(FATAL,
314+
"could not find free slot for condition of injection point %s",
315+
name);
316+
}
317+
185318
PG_RETURN_VOID();
186319
}
187320

@@ -235,6 +368,32 @@ injection_points_wakeup(PG_FUNCTION_ARGS)
235368
PG_RETURN_VOID();
236369
}
237370

371+
/*
372+
* injection_points_set_local
373+
*
374+
* Track if any injection point created in this process ought to run only
375+
* in this process. Such injection points are detached automatically when
376+
* this process exits. This is useful to make test suites concurrent-safe.
377+
*/
378+
PG_FUNCTION_INFO_V1(injection_points_set_local);
379+
Datum
380+
injection_points_set_local(PG_FUNCTION_ARGS)
381+
{
382+
/* Enable flag to add a runtime condition based on this process ID */
383+
injection_point_local = true;
384+
385+
if (inj_state == NULL)
386+
injection_init_shmem();
387+
388+
/*
389+
* Register a before_shmem_exit callback to remove any injection points
390+
* linked to this process.
391+
*/
392+
before_shmem_exit(injection_points_cleanup, (Datum) 0);
393+
394+
PG_RETURN_VOID();
395+
}
396+
238397
/*
239398
* SQL function for dropping an injection point.
240399
*/
@@ -246,5 +405,22 @@ injection_points_detach(PG_FUNCTION_ARGS)
246405

247406
InjectionPointDetach(name);
248407

408+
if (inj_state == NULL)
409+
injection_init_shmem();
410+
411+
/* Clean up any conditions associated to this injection point */
412+
SpinLockAcquire(&inj_state->lock);
413+
for (int i = 0; i < INJ_MAX_CONDITION; i++)
414+
{
415+
InjectionPointCondition *condition = &inj_state->conditions[i];
416+
417+
if (strcmp(condition->name, name) == 0)
418+
{
419+
condition->pid = 0;
420+
condition->name[0] = '\0';
421+
}
422+
}
423+
SpinLockRelease(&inj_state->lock);
424+
249425
PG_RETURN_VOID();
250426
}

src/test/modules/injection_points/sql/injection_points.sql

+20
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,25 @@ SELECT injection_points_run('TestInjectionLog2'); -- notice
3030
SELECT injection_points_detach('TestInjectionLog'); -- fails
3131

3232
SELECT injection_points_run('TestInjectionLog2'); -- notice
33+
SELECT injection_points_detach('TestInjectionLog2');
34+
35+
-- Runtime conditions
36+
SELECT injection_points_attach('TestConditionError', 'error');
37+
-- Any follow-up injection point attached will be local to this process.
38+
SELECT injection_points_set_local();
39+
SELECT injection_points_attach('TestConditionLocal1', 'error');
40+
SELECT injection_points_attach('TestConditionLocal2', 'notice');
41+
SELECT injection_points_run('TestConditionLocal1'); -- error
42+
SELECT injection_points_run('TestConditionLocal2'); -- notice
43+
-- reload, local injection points should be gone.
44+
\c
45+
SELECT injection_points_run('TestConditionLocal1'); -- nothing
46+
SELECT injection_points_run('TestConditionLocal2'); -- nothing
47+
SELECT injection_points_run('TestConditionError'); -- error
48+
SELECT injection_points_detach('TestConditionError');
49+
-- Attaching injection points that use the same name as one defined locally
50+
-- previously should work.
51+
SELECT injection_points_attach('TestConditionLocal1', 'error');
52+
SELECT injection_points_detach('TestConditionLocal1');
3353

3454
DROP EXTENSION injection_points;

src/tools/pgindent/typedefs.list

+1
Original file line numberDiff line numberDiff line change
@@ -1219,6 +1219,7 @@ InitSampleScan_function
12191219
InitializeDSMForeignScan_function
12201220
InitializeWorkerForeignScan_function
12211221
InjectionPointCacheEntry
1222+
InjectionPointCondition
12221223
InjectionPointEntry
12231224
InjectionPointSharedState
12241225
InlineCodeBlock

0 commit comments

Comments
 (0)