1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
/*-------------------------------------------------------------------------
*
* delay_execution.c
* Test module to introduce delay at various points during execution of a
* query to test that execution proceeds safely in light of concurrent
* changes.
*
* The delay is implemented by taking and immediately releasing a specified
* advisory lock. If another process has previously taken that lock, the
* current process will be blocked until the lock is released; otherwise,
* there's no effect. This allows an isolationtester script to reliably
* test behaviors where some specified action happens in another backend in
* a couple of cases: 1) between parsing and execution of any desired query
* when using the planner_hook, 2) between RevalidateCachedQuery() and
* ExecutorStart() when using the ExecutorStart_hook.
*
* Copyright (c) 2020-2025, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/modules/delay_execution/delay_execution.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <limits.h>
#include "executor/executor.h"
#include "optimizer/planner.h"
#include "utils/fmgrprotos.h"
#include "utils/guc.h"
#include "utils/inval.h"
PG_MODULE_MAGIC;
/* GUC: advisory lock ID to use. Zero disables the feature. */
static int post_planning_lock_id = 0;
static int executor_start_lock_id = 0;
/* Save previous hook users to be a good citizen */
static planner_hook_type prev_planner_hook = NULL;
static ExecutorStart_hook_type prev_ExecutorStart_hook = NULL;
/* planner_hook function to provide the desired delay */
static PlannedStmt *
delay_execution_planner(Query *parse, const char *query_string,
int cursorOptions, ParamListInfo boundParams)
{
PlannedStmt *result;
/* Invoke the planner, possibly via a previous hook user */
if (prev_planner_hook)
result = prev_planner_hook(parse, query_string, cursorOptions,
boundParams);
else
result = standard_planner(parse, query_string, cursorOptions,
boundParams);
/* If enabled, delay by taking and releasing the specified lock */
if (post_planning_lock_id != 0)
{
DirectFunctionCall1(pg_advisory_lock_int8,
Int64GetDatum((int64) post_planning_lock_id));
DirectFunctionCall1(pg_advisory_unlock_int8,
Int64GetDatum((int64) post_planning_lock_id));
/*
* Ensure that we notice any pending invalidations, since the advisory
* lock functions don't do this.
*/
AcceptInvalidationMessages();
}
return result;
}
/* ExecutorStart_hook function to provide the desired delay */
static bool
delay_execution_ExecutorStart(QueryDesc *queryDesc, int eflags)
{
bool plan_valid;
/* If enabled, delay by taking and releasing the specified lock */
if (executor_start_lock_id != 0)
{
DirectFunctionCall1(pg_advisory_lock_int8,
Int64GetDatum((int64) executor_start_lock_id));
DirectFunctionCall1(pg_advisory_unlock_int8,
Int64GetDatum((int64) executor_start_lock_id));
/*
* Ensure that we notice any pending invalidations, since the advisory
* lock functions don't do this.
*/
AcceptInvalidationMessages();
}
/* Now start the executor, possibly via a previous hook user */
if (prev_ExecutorStart_hook)
plan_valid = prev_ExecutorStart_hook(queryDesc, eflags);
else
plan_valid = standard_ExecutorStart(queryDesc, eflags);
if (executor_start_lock_id != 0)
elog(NOTICE, "Finished ExecutorStart(): CachedPlan is %s",
plan_valid ? "valid" : "not valid");
return plan_valid;
}
/* Module load function */
void
_PG_init(void)
{
/* Set up GUCs to control which lock is used */
DefineCustomIntVariable("delay_execution.post_planning_lock_id",
"Sets the advisory lock ID to be locked/unlocked after planning.",
"Zero disables the delay.",
&post_planning_lock_id,
0,
0, INT_MAX,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
DefineCustomIntVariable("delay_execution.executor_start_lock_id",
"Sets the advisory lock ID to be locked/unlocked before starting execution.",
"Zero disables the delay.",
&executor_start_lock_id,
0,
0, INT_MAX,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
MarkGUCPrefixReserved("delay_execution");
/* Install our hooks. */
prev_planner_hook = planner_hook;
planner_hook = delay_execution_planner;
prev_ExecutorStart_hook = ExecutorStart_hook;
ExecutorStart_hook = delay_execution_ExecutorStart;
}
|