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