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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
|
/*--------------------------------------------------------------------------
*
* test_resowner_many.c
* Test ResourceOwner functionality with lots of resources
*
* Copyright (c) 2022-2024, PostgreSQL Global Development Group
*
* IDENTIFICATION
* src/test/modules/test_resowner/test_resowner_many.c
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include "lib/ilist.h"
#include "utils/memutils.h"
#include "utils/resowner.h"
/*
* Define a custom resource type to use in the test. The resource being
* tracked is a palloc'd ManyTestResource struct.
*
* To cross-check that the ResourceOwner calls the callback functions
* correctly, we keep track of the remembered resources ourselves in a linked
* list, and also keep counters of how many times the callback functions have
* been called.
*/
typedef struct
{
ResourceOwnerDesc desc;
int nremembered;
int nforgotten;
int nreleased;
int nleaked;
dlist_head current_resources;
} ManyTestResourceKind;
typedef struct
{
ManyTestResourceKind *kind;
dlist_node node;
} ManyTestResource;
/*
* Current release phase, and priority of last call to the release callback.
* This is used to check that the resources are released in correct order.
*/
static ResourceReleasePhase current_release_phase;
static uint32 last_release_priority = 0;
/* prototypes for local functions */
static void ReleaseManyTestResource(Datum res);
static char *PrintManyTest(Datum res);
static void InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
ResourceReleasePhase phase, uint32 priority);
static void RememberManyTestResources(ResourceOwner owner,
ManyTestResourceKind *kinds, int nkinds,
int nresources);
static void ForgetManyTestResources(ResourceOwner owner,
ManyTestResourceKind *kinds, int nkinds,
int nresources);
static int GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds);
/* ResourceOwner callback */
static void
ReleaseManyTestResource(Datum res)
{
ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
elog(DEBUG1, "releasing resource %p from %s", mres, mres->kind->desc.name);
Assert(last_release_priority <= mres->kind->desc.release_priority);
dlist_delete(&mres->node);
mres->kind->nreleased++;
last_release_priority = mres->kind->desc.release_priority;
pfree(mres);
}
/* ResourceOwner callback */
static char *
PrintManyTest(Datum res)
{
ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
/*
* XXX: we assume that the DebugPrint function is called once for each
* leaked resource, and that there are no other callers.
*/
mres->kind->nleaked++;
return psprintf("many-test resource from %s", mres->kind->desc.name);
}
static void
InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
ResourceReleasePhase phase, uint32 priority)
{
kind->desc.name = name;
kind->desc.release_phase = phase;
kind->desc.release_priority = priority;
kind->desc.ReleaseResource = ReleaseManyTestResource;
kind->desc.DebugPrint = PrintManyTest;
kind->nremembered = 0;
kind->nforgotten = 0;
kind->nreleased = 0;
kind->nleaked = 0;
dlist_init(&kind->current_resources);
}
/*
* Remember 'nresources' resources. The resources are remembered in round
* robin fashion with the kinds from 'kinds' array.
*/
static void
RememberManyTestResources(ResourceOwner owner,
ManyTestResourceKind *kinds, int nkinds,
int nresources)
{
int kind_idx = 0;
for (int i = 0; i < nresources; i++)
{
ManyTestResource *mres = palloc(sizeof(ManyTestResource));
mres->kind = &kinds[kind_idx];
dlist_node_init(&mres->node);
ResourceOwnerEnlarge(owner);
ResourceOwnerRemember(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
kinds[kind_idx].nremembered++;
dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.name);
kind_idx = (kind_idx + 1) % nkinds;
}
}
/*
* Forget 'nresources' resources, in round robin fashion from 'kinds'.
*/
static void
ForgetManyTestResources(ResourceOwner owner,
ManyTestResourceKind *kinds, int nkinds,
int nresources)
{
int kind_idx = 0;
int ntotal;
ntotal = GetTotalResourceCount(kinds, nkinds);
if (ntotal < nresources)
elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
for (int i = 0; i < nresources; i++)
{
bool found = false;
for (int j = 0; j < nkinds; j++)
{
kind_idx = (kind_idx + 1) % nkinds;
if (!dlist_is_empty(&kinds[kind_idx].current_resources))
{
ManyTestResource *mres = dlist_head_element(ManyTestResource, node, &kinds[kind_idx].current_resources);
ResourceOwnerForget(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
kinds[kind_idx].nforgotten++;
dlist_delete(&mres->node);
pfree(mres);
found = true;
break;
}
}
if (!found)
elog(ERROR, "could not find a test resource to forget");
}
}
/*
* Get total number of currently active resources among 'kinds'.
*/
static int
GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds)
{
int ntotal = 0;
for (int i = 0; i < nkinds; i++)
ntotal += kinds[i].nremembered - kinds[i].nforgotten - kinds[i].nreleased;
return ntotal;
}
/*
* Remember lots of resources, belonging to 'nkinds' different resource types
* with different priorities. Then forget some of them, and finally, release
* the resource owner. We use a custom resource type that performs various
* sanity checks to verify that all the resources are released, and in the
* correct order.
*/
PG_FUNCTION_INFO_V1(test_resowner_many);
Datum
test_resowner_many(PG_FUNCTION_ARGS)
{
int32 nkinds = PG_GETARG_INT32(0);
int32 nremember_bl = PG_GETARG_INT32(1);
int32 nforget_bl = PG_GETARG_INT32(2);
int32 nremember_al = PG_GETARG_INT32(3);
int32 nforget_al = PG_GETARG_INT32(4);
ResourceOwner resowner;
ManyTestResourceKind *before_kinds;
ManyTestResourceKind *after_kinds;
/* Sanity check the arguments */
if (nkinds < 0)
elog(ERROR, "nkinds must be >= 0");
if (nremember_bl < 0)
elog(ERROR, "nremember_bl must be >= 0");
if (nforget_bl < 0 || nforget_bl > nremember_bl)
elog(ERROR, "nforget_bl must between 0 and 'nremember_bl'");
if (nremember_al < 0)
elog(ERROR, "nremember_al must be greater than zero");
if (nforget_al < 0 || nforget_al > nremember_al)
elog(ERROR, "nforget_al must between 0 and 'nremember_al'");
/* Initialize all the different resource kinds to use */
before_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
for (int i = 0; i < nkinds; i++)
{
InitManyTestResourceKind(&before_kinds[i],
psprintf("resource before locks %d", i),
RESOURCE_RELEASE_BEFORE_LOCKS,
RELEASE_PRIO_FIRST + i);
}
after_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
for (int i = 0; i < nkinds; i++)
{
InitManyTestResourceKind(&after_kinds[i],
psprintf("resource after locks %d", i),
RESOURCE_RELEASE_AFTER_LOCKS,
RELEASE_PRIO_FIRST + i);
}
resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
/* Remember a bunch of resources */
if (nremember_bl > 0)
{
elog(NOTICE, "remembering %d before-locks resources", nremember_bl);
RememberManyTestResources(resowner, before_kinds, nkinds, nremember_bl);
}
if (nremember_al > 0)
{
elog(NOTICE, "remembering %d after-locks resources", nremember_al);
RememberManyTestResources(resowner, after_kinds, nkinds, nremember_al);
}
/* Forget what was remembered */
if (nforget_bl > 0)
{
elog(NOTICE, "forgetting %d before-locks resources", nforget_bl);
ForgetManyTestResources(resowner, before_kinds, nkinds, nforget_bl);
}
if (nforget_al > 0)
{
elog(NOTICE, "forgetting %d after-locks resources", nforget_al);
ForgetManyTestResources(resowner, after_kinds, nkinds, nforget_al);
}
/* Start releasing */
elog(NOTICE, "releasing resources before locks");
current_release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
last_release_priority = 0;
ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
elog(NOTICE, "releasing locks");
current_release_phase = RESOURCE_RELEASE_LOCKS;
last_release_priority = 0;
ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, false, false);
elog(NOTICE, "releasing resources after locks");
current_release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
last_release_priority = 0;
ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
Assert(GetTotalResourceCount(after_kinds, nkinds) == 0);
ResourceOwnerDelete(resowner);
PG_RETURN_VOID();
}
|