Line data Source code
1 : /*--------------------------------------------------------------------------
2 : *
3 : * test_resowner_many.c
4 : * Test ResourceOwner functionality with lots of resources
5 : *
6 : * Copyright (c) 2022-2024, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * src/test/modules/test_resowner/test_resowner_many.c
10 : *
11 : * -------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "fmgr.h"
16 : #include "lib/ilist.h"
17 : #include "utils/resowner.h"
18 :
19 : /*
20 : * Define a custom resource type to use in the test. The resource being
21 : * tracked is a palloc'd ManyTestResource struct.
22 : *
23 : * To cross-check that the ResourceOwner calls the callback functions
24 : * correctly, we keep track of the remembered resources ourselves in a linked
25 : * list, and also keep counters of how many times the callback functions have
26 : * been called.
27 : */
28 : typedef struct
29 : {
30 : ResourceOwnerDesc desc;
31 : int nremembered;
32 : int nforgotten;
33 : int nreleased;
34 : int nleaked;
35 :
36 : dlist_head current_resources;
37 : } ManyTestResourceKind;
38 :
39 : typedef struct
40 : {
41 : ManyTestResourceKind *kind;
42 : dlist_node node;
43 : } ManyTestResource;
44 :
45 : /*
46 : * Current release phase, and priority of last call to the release callback.
47 : * This is used to check that the resources are released in correct order.
48 : */
49 : static ResourceReleasePhase current_release_phase;
50 : static uint32 last_release_priority = 0;
51 :
52 : /* prototypes for local functions */
53 : static void ReleaseManyTestResource(Datum res);
54 : static char *PrintManyTest(Datum res);
55 : static void InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
56 : ResourceReleasePhase phase, uint32 priority);
57 : static void RememberManyTestResources(ResourceOwner owner,
58 : ManyTestResourceKind *kinds, int nkinds,
59 : int nresources);
60 : static void ForgetManyTestResources(ResourceOwner owner,
61 : ManyTestResourceKind *kinds, int nkinds,
62 : int nresources);
63 : static int GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds);
64 :
65 : /* ResourceOwner callback */
66 : static void
67 398000 : ReleaseManyTestResource(Datum res)
68 : {
69 398000 : ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
70 :
71 398000 : elog(DEBUG1, "releasing resource %p from %s", mres, mres->kind->desc.name);
72 : Assert(last_release_priority <= mres->kind->desc.release_priority);
73 :
74 398000 : dlist_delete(&mres->node);
75 398000 : mres->kind->nreleased++;
76 398000 : last_release_priority = mres->kind->desc.release_priority;
77 398000 : pfree(mres);
78 398000 : }
79 :
80 : /* ResourceOwner callback */
81 : static char *
82 0 : PrintManyTest(Datum res)
83 : {
84 0 : ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
85 :
86 : /*
87 : * XXX: we assume that the DebugPrint function is called once for each
88 : * leaked resource, and that there are no other callers.
89 : */
90 0 : mres->kind->nleaked++;
91 :
92 0 : return psprintf("many-test resource from %s", mres->kind->desc.name);
93 : }
94 :
95 : static void
96 12 : InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
97 : ResourceReleasePhase phase, uint32 priority)
98 : {
99 12 : kind->desc.name = name;
100 12 : kind->desc.release_phase = phase;
101 12 : kind->desc.release_priority = priority;
102 12 : kind->desc.ReleaseResource = ReleaseManyTestResource;
103 12 : kind->desc.DebugPrint = PrintManyTest;
104 12 : kind->nremembered = 0;
105 12 : kind->nforgotten = 0;
106 12 : kind->nreleased = 0;
107 12 : kind->nleaked = 0;
108 12 : dlist_init(&kind->current_resources);
109 12 : }
110 :
111 : /*
112 : * Remember 'nresources' resources. The resources are remembered in round
113 : * robin fashion with the kinds from 'kinds' array.
114 : */
115 : static void
116 4 : RememberManyTestResources(ResourceOwner owner,
117 : ManyTestResourceKind *kinds, int nkinds,
118 : int nresources)
119 : {
120 4 : int kind_idx = 0;
121 :
122 400004 : for (int i = 0; i < nresources; i++)
123 : {
124 400000 : ManyTestResource *mres = palloc(sizeof(ManyTestResource));
125 :
126 400000 : mres->kind = &kinds[kind_idx];
127 400000 : dlist_node_init(&mres->node);
128 :
129 400000 : ResourceOwnerEnlarge(owner);
130 400000 : ResourceOwnerRemember(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
131 400000 : kinds[kind_idx].nremembered++;
132 400000 : dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
133 :
134 400000 : elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.name);
135 :
136 400000 : kind_idx = (kind_idx + 1) % nkinds;
137 : }
138 4 : }
139 :
140 : /*
141 : * Forget 'nresources' resources, in round robin fashion from 'kinds'.
142 : */
143 : static void
144 4 : ForgetManyTestResources(ResourceOwner owner,
145 : ManyTestResourceKind *kinds, int nkinds,
146 : int nresources)
147 : {
148 4 : int kind_idx = 0;
149 : int ntotal;
150 :
151 4 : ntotal = GetTotalResourceCount(kinds, nkinds);
152 4 : if (ntotal < nresources)
153 0 : elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
154 :
155 2004 : for (int i = 0; i < nresources; i++)
156 : {
157 2000 : bool found = false;
158 :
159 2000 : for (int j = 0; j < nkinds; j++)
160 : {
161 2000 : kind_idx = (kind_idx + 1) % nkinds;
162 2000 : if (!dlist_is_empty(&kinds[kind_idx].current_resources))
163 : {
164 2000 : ManyTestResource *mres = dlist_head_element(ManyTestResource, node, &kinds[kind_idx].current_resources);
165 :
166 2000 : ResourceOwnerForget(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
167 2000 : kinds[kind_idx].nforgotten++;
168 2000 : dlist_delete(&mres->node);
169 2000 : pfree(mres);
170 :
171 2000 : found = true;
172 2000 : break;
173 : }
174 : }
175 2000 : if (!found)
176 0 : elog(ERROR, "could not find a test resource to forget");
177 : }
178 4 : }
179 :
180 : /*
181 : * Get total number of currently active resources among 'kinds'.
182 : */
183 : static int
184 4 : GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds)
185 : {
186 4 : int ntotal = 0;
187 :
188 16 : for (int i = 0; i < nkinds; i++)
189 12 : ntotal += kinds[i].nremembered - kinds[i].nforgotten - kinds[i].nreleased;
190 :
191 4 : return ntotal;
192 : }
193 :
194 : /*
195 : * Remember lots of resources, belonging to 'nkinds' different resource types
196 : * with different priorities. Then forget some of them, and finally, release
197 : * the resource owner. We use a custom resource type that performs various
198 : * sanity checks to verify that all the resources are released, and in the
199 : * correct order.
200 : */
201 4 : PG_FUNCTION_INFO_V1(test_resowner_many);
202 : Datum
203 2 : test_resowner_many(PG_FUNCTION_ARGS)
204 : {
205 2 : int32 nkinds = PG_GETARG_INT32(0);
206 2 : int32 nremember_bl = PG_GETARG_INT32(1);
207 2 : int32 nforget_bl = PG_GETARG_INT32(2);
208 2 : int32 nremember_al = PG_GETARG_INT32(3);
209 2 : int32 nforget_al = PG_GETARG_INT32(4);
210 :
211 : ResourceOwner resowner;
212 :
213 : ManyTestResourceKind *before_kinds;
214 : ManyTestResourceKind *after_kinds;
215 :
216 : /* Sanity check the arguments */
217 2 : if (nkinds < 0)
218 0 : elog(ERROR, "nkinds must be >= 0");
219 2 : if (nremember_bl < 0)
220 0 : elog(ERROR, "nremember_bl must be >= 0");
221 2 : if (nforget_bl < 0 || nforget_bl > nremember_bl)
222 0 : elog(ERROR, "nforget_bl must between 0 and 'nremember_bl'");
223 2 : if (nremember_al < 0)
224 0 : elog(ERROR, "nremember_al must be greater than zero");
225 2 : if (nforget_al < 0 || nforget_al > nremember_al)
226 0 : elog(ERROR, "nforget_al must between 0 and 'nremember_al'");
227 :
228 : /* Initialize all the different resource kinds to use */
229 2 : before_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
230 8 : for (int i = 0; i < nkinds; i++)
231 : {
232 6 : InitManyTestResourceKind(&before_kinds[i],
233 : psprintf("resource before locks %d", i),
234 : RESOURCE_RELEASE_BEFORE_LOCKS,
235 6 : RELEASE_PRIO_FIRST + i);
236 : }
237 2 : after_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
238 8 : for (int i = 0; i < nkinds; i++)
239 : {
240 6 : InitManyTestResourceKind(&after_kinds[i],
241 : psprintf("resource after locks %d", i),
242 : RESOURCE_RELEASE_AFTER_LOCKS,
243 6 : RELEASE_PRIO_FIRST + i);
244 : }
245 :
246 2 : resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
247 :
248 : /* Remember a bunch of resources */
249 2 : if (nremember_bl > 0)
250 : {
251 2 : elog(NOTICE, "remembering %d before-locks resources", nremember_bl);
252 2 : RememberManyTestResources(resowner, before_kinds, nkinds, nremember_bl);
253 : }
254 2 : if (nremember_al > 0)
255 : {
256 2 : elog(NOTICE, "remembering %d after-locks resources", nremember_al);
257 2 : RememberManyTestResources(resowner, after_kinds, nkinds, nremember_al);
258 : }
259 :
260 : /* Forget what was remembered */
261 2 : if (nforget_bl > 0)
262 : {
263 2 : elog(NOTICE, "forgetting %d before-locks resources", nforget_bl);
264 2 : ForgetManyTestResources(resowner, before_kinds, nkinds, nforget_bl);
265 : }
266 :
267 2 : if (nforget_al > 0)
268 : {
269 2 : elog(NOTICE, "forgetting %d after-locks resources", nforget_al);
270 2 : ForgetManyTestResources(resowner, after_kinds, nkinds, nforget_al);
271 : }
272 :
273 : /* Start releasing */
274 2 : elog(NOTICE, "releasing resources before locks");
275 2 : current_release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
276 2 : last_release_priority = 0;
277 2 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
278 : Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
279 :
280 2 : elog(NOTICE, "releasing locks");
281 2 : current_release_phase = RESOURCE_RELEASE_LOCKS;
282 2 : last_release_priority = 0;
283 2 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, false, false);
284 :
285 2 : elog(NOTICE, "releasing resources after locks");
286 2 : current_release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
287 2 : last_release_priority = 0;
288 2 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
289 : Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
290 : Assert(GetTotalResourceCount(after_kinds, nkinds) == 0);
291 :
292 2 : ResourceOwnerDelete(resowner);
293 :
294 2 : PG_RETURN_VOID();
295 : }
|