Skip to content

Commit 267d41d

Browse files
committed
injection_points: Store runtime conditions in private area
This commit fixes a race condition between injection point run and detach, where a point detached by a backend and concurrently running in a second backend could cause the second backend to do an incorrect condition check. This issue happens because the second backend retrieves the callback information in a first step in the shmem hash table for injection points, and the condition in a second step within the callback. If the point is detached between these two steps, the condition would be removed, causing the point to run while it should not. Storing the condition in the new private_data area introduced in 33181b4 ensures that the condition retrieved is consistent with its callback. This commit leads to a lot of simplifications in the module injection_points, as there is no need to handle the runtime conditions inside it anymore. Runtime conditions have no more a maximum number. Per discussion with Noah Misch. Reviewed-by: Noah Misch Discussion: https://postgr.es/m/20240509031553.47@rfd.leadboat.com
1 parent 33181b4 commit 267d41d

File tree

2 files changed

+61
-113
lines changed

2 files changed

+61
-113
lines changed

src/test/modules/injection_points/injection_points.c

+60-113
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,54 @@
1919

2020
#include "fmgr.h"
2121
#include "miscadmin.h"
22+
#include "nodes/pg_list.h"
23+
#include "nodes/value.h"
2224
#include "storage/condition_variable.h"
2325
#include "storage/dsm_registry.h"
2426
#include "storage/ipc.h"
2527
#include "storage/lwlock.h"
2628
#include "storage/shmem.h"
2729
#include "utils/builtins.h"
2830
#include "utils/injection_point.h"
31+
#include "utils/memutils.h"
2932
#include "utils/wait_event.h"
3033

3134
PG_MODULE_MAGIC;
3235

3336
/* Maximum number of waits usable in injection points at once */
3437
#define INJ_MAX_WAIT 8
3538
#define INJ_NAME_MAXLEN 64
36-
#define INJ_MAX_CONDITION 4
3739

3840
/*
3941
* Conditions related to injection points. This tracks in shared memory the
40-
* runtime conditions under which an injection point is allowed to run.
42+
* runtime conditions under which an injection point is allowed to run,
43+
* stored as private_data when an injection point is attached, and passed as
44+
* argument to the callback.
4145
*
4246
* If more types of runtime conditions need to be tracked, this structure
4347
* should be expanded.
4448
*/
49+
typedef enum InjectionPointConditionType
50+
{
51+
INJ_CONDITION_ALWAYS = 0, /* always run */
52+
INJ_CONDITION_PID, /* PID restriction */
53+
} InjectionPointConditionType;
54+
4555
typedef struct InjectionPointCondition
4656
{
47-
/* Name of the injection point related to this condition */
48-
char name[INJ_NAME_MAXLEN];
57+
/* Type of the condition */
58+
InjectionPointConditionType type;
4959

5060
/* ID of the process where the injection point is allowed to run */
5161
int pid;
5262
} InjectionPointCondition;
5363

64+
/*
65+
* List of injection points stored in TopMemoryContext attached
66+
* locally to this process.
67+
*/
68+
static List *inj_list_local = NIL;
69+
5470
/* Shared state information for injection points. */
5571
typedef struct InjectionPointSharedState
5672
{
@@ -65,9 +81,6 @@ typedef struct InjectionPointSharedState
6581

6682
/* Condition variable used for waits and wakeups */
6783
ConditionVariable wait_point;
68-
69-
/* Conditions to run an injection point */
70-
InjectionPointCondition conditions[INJ_MAX_CONDITION];
7184
} InjectionPointSharedState;
7285

7386
/* Pointer to shared-memory state. */
@@ -94,7 +107,6 @@ injection_point_init_state(void *ptr)
94107
SpinLockInit(&state->lock);
95108
memset(state->wait_counts, 0, sizeof(state->wait_counts));
96109
memset(state->name, 0, sizeof(state->name));
97-
memset(state->conditions, 0, sizeof(state->conditions));
98110
ConditionVariableInit(&state->wait_point);
99111
}
100112

@@ -119,39 +131,23 @@ injection_init_shmem(void)
119131
* Check runtime conditions associated to an injection point.
120132
*
121133
* Returns true if the named injection point is allowed to run, and false
122-
* otherwise. Multiple conditions can be associated to a single injection
123-
* point, so check them all.
134+
* otherwise.
124135
*/
125136
static bool
126-
injection_point_allowed(const char *name)
137+
injection_point_allowed(InjectionPointCondition *condition)
127138
{
128139
bool result = true;
129140

130-
if (inj_state == NULL)
131-
injection_init_shmem();
132-
133-
SpinLockAcquire(&inj_state->lock);
134-
135-
for (int i = 0; i < INJ_MAX_CONDITION; i++)
141+
switch (condition->type)
136142
{
137-
InjectionPointCondition *condition = &inj_state->conditions[i];
138-
139-
if (strcmp(condition->name, name) == 0)
140-
{
141-
/*
142-
* Check if this injection point is allowed to run in this
143-
* process.
144-
*/
143+
case INJ_CONDITION_PID:
145144
if (MyProcPid != condition->pid)
146-
{
147145
result = false;
148-
break;
149-
}
150-
}
146+
break;
147+
case INJ_CONDITION_ALWAYS:
148+
break;
151149
}
152150

153-
SpinLockRelease(&inj_state->lock);
154-
155151
return result;
156152
}
157153

@@ -162,61 +158,28 @@ injection_point_allowed(const char *name)
162158
static void
163159
injection_points_cleanup(int code, Datum arg)
164160
{
165-
char names[INJ_MAX_CONDITION][INJ_NAME_MAXLEN] = {0};
166-
int count = 0;
161+
ListCell *lc;
167162

168163
/* Leave if nothing is tracked locally */
169164
if (!injection_point_local)
170165
return;
171166

172-
/*
173-
* This is done in three steps: detect the points to detach, detach them
174-
* and release their conditions.
175-
*/
176-
SpinLockAcquire(&inj_state->lock);
177-
for (int i = 0; i < INJ_MAX_CONDITION; i++)
178-
{
179-
InjectionPointCondition *condition = &inj_state->conditions[i];
180-
181-
if (condition->name[0] == '\0')
182-
continue;
183-
184-
if (condition->pid != MyProcPid)
185-
continue;
186-
187-
/* Extract the point name to detach */
188-
strlcpy(names[count], condition->name, INJ_NAME_MAXLEN);
189-
count++;
190-
}
191-
SpinLockRelease(&inj_state->lock);
192-
193-
/* Detach, without holding the spinlock */
194-
for (int i = 0; i < count; i++)
195-
(void) InjectionPointDetach(names[i]);
196-
197-
/* Clear all the conditions */
198-
SpinLockAcquire(&inj_state->lock);
199-
for (int i = 0; i < INJ_MAX_CONDITION; i++)
167+
/* Detach all the local points */
168+
foreach(lc, inj_list_local)
200169
{
201-
InjectionPointCondition *condition = &inj_state->conditions[i];
170+
char *name = strVal(lfirst(lc));
202171

203-
if (condition->name[0] == '\0')
204-
continue;
205-
206-
if (condition->pid != MyProcPid)
207-
continue;
208-
209-
condition->name[0] = '\0';
210-
condition->pid = 0;
172+
(void) InjectionPointDetach(name);
211173
}
212-
SpinLockRelease(&inj_state->lock);
213174
}
214175

215176
/* Set of callbacks available to be attached to an injection point. */
216177
void
217178
injection_error(const char *name, const void *private_data)
218179
{
219-
if (!injection_point_allowed(name))
180+
InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
181+
182+
if (!injection_point_allowed(condition))
220183
return;
221184

222185
elog(ERROR, "error triggered for injection point %s", name);
@@ -225,7 +188,9 @@ injection_error(const char *name, const void *private_data)
225188
void
226189
injection_notice(const char *name, const void *private_data)
227190
{
228-
if (!injection_point_allowed(name))
191+
InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
192+
193+
if (!injection_point_allowed(condition))
229194
return;
230195

231196
elog(NOTICE, "notice triggered for injection point %s", name);
@@ -238,11 +203,12 @@ injection_wait(const char *name, const void *private_data)
238203
uint32 old_wait_counts = 0;
239204
int index = -1;
240205
uint32 injection_wait_event = 0;
206+
InjectionPointCondition *condition = (InjectionPointCondition *) private_data;
241207

242208
if (inj_state == NULL)
243209
injection_init_shmem();
244210

245-
if (!injection_point_allowed(name))
211+
if (!injection_point_allowed(condition))
246212
return;
247213

248214
/*
@@ -304,6 +270,7 @@ injection_points_attach(PG_FUNCTION_ARGS)
304270
char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
305271
char *action = text_to_cstring(PG_GETARG_TEXT_PP(1));
306272
char *function;
273+
InjectionPointCondition condition = {0};
307274

308275
if (strcmp(action, "error") == 0)
309276
function = "injection_error";
@@ -314,37 +281,24 @@ injection_points_attach(PG_FUNCTION_ARGS)
314281
else
315282
elog(ERROR, "incorrect action \"%s\" for injection point creation", action);
316283

317-
InjectionPointAttach(name, "injection_points", function, NULL, 0);
318-
319284
if (injection_point_local)
320285
{
321-
int index = -1;
286+
condition.type = INJ_CONDITION_PID;
287+
condition.pid = MyProcPid;
288+
}
322289

323-
/*
324-
* Register runtime condition to link this injection point to the
325-
* current process.
326-
*/
327-
SpinLockAcquire(&inj_state->lock);
328-
for (int i = 0; i < INJ_MAX_CONDITION; i++)
329-
{
330-
InjectionPointCondition *condition = &inj_state->conditions[i];
331-
332-
if (condition->name[0] == '\0')
333-
{
334-
index = i;
335-
strlcpy(condition->name, name, INJ_NAME_MAXLEN);
336-
condition->pid = MyProcPid;
337-
break;
338-
}
339-
}
340-
SpinLockRelease(&inj_state->lock);
290+
InjectionPointAttach(name, "injection_points", function, &condition,
291+
sizeof(InjectionPointCondition));
341292

342-
if (index < 0)
343-
elog(FATAL,
344-
"could not find free slot for condition of injection point %s",
345-
name);
346-
}
293+
if (injection_point_local)
294+
{
295+
MemoryContext oldctx;
347296

297+
/* Local injection point, so track it for automated cleanup */
298+
oldctx = MemoryContextSwitchTo(TopMemoryContext);
299+
inj_list_local = lappend(inj_list_local, makeString(pstrdup(name)));
300+
MemoryContextSwitchTo(oldctx);
301+
}
348302
PG_RETURN_VOID();
349303
}
350304

@@ -436,22 +390,15 @@ injection_points_detach(PG_FUNCTION_ARGS)
436390
if (!InjectionPointDetach(name))
437391
elog(ERROR, "could not detach injection point \"%s\"", name);
438392

439-
if (inj_state == NULL)
440-
injection_init_shmem();
441-
442-
/* Clean up any conditions associated to this injection point */
443-
SpinLockAcquire(&inj_state->lock);
444-
for (int i = 0; i < INJ_MAX_CONDITION; i++)
393+
/* Remove point from local list, if required */
394+
if (inj_list_local != NIL)
445395
{
446-
InjectionPointCondition *condition = &inj_state->conditions[i];
396+
MemoryContext oldctx;
447397

448-
if (strcmp(condition->name, name) == 0)
449-
{
450-
condition->pid = 0;
451-
condition->name[0] = '\0';
452-
}
398+
oldctx = MemoryContextSwitchTo(TopMemoryContext);
399+
inj_list_local = list_delete(inj_list_local, makeString(name));
400+
MemoryContextSwitchTo(oldctx);
453401
}
454-
SpinLockRelease(&inj_state->lock);
455402

456403
PG_RETURN_VOID();
457404
}

src/tools/pgindent/typedefs.list

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

0 commit comments

Comments
 (0)