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

Lines changed: 77 additions & 0 deletions
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

Lines changed: 11 additions & 0 deletions
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

Lines changed: 176 additions & 0 deletions
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

Lines changed: 20 additions & 0 deletions
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

Lines changed: 1 addition & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy