Age Owner Branch data TLA Line data Source code
1 : : /*--------------------------------------------------------------------------
2 : : *
3 : : * test_resowner_many.c
4 : : * Test ResourceOwner functionality with lots of resources
5 : : *
6 : : * Copyright (c) 2022-2025, 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
668 heikki.linnakangas@i 67 :CBC 199000 : ReleaseManyTestResource(Datum res)
68 : : {
69 : 199000 : ManyTestResource *mres = (ManyTestResource *) DatumGetPointer(res);
70 : :
71 [ - + ]: 199000 : elog(DEBUG1, "releasing resource %p from %s", mres, mres->kind->desc.name);
72 [ - + ]: 199000 : Assert(last_release_priority <= mres->kind->desc.release_priority);
73 : :
74 : 199000 : dlist_delete(&mres->node);
75 : 199000 : mres->kind->nreleased++;
76 : 199000 : last_release_priority = mres->kind->desc.release_priority;
77 : 199000 : pfree(mres);
78 : 199000 : }
79 : :
80 : : /* ResourceOwner callback */
81 : : static char *
668 heikki.linnakangas@i 82 :UBC 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
668 heikki.linnakangas@i 96 :CBC 6 : InitManyTestResourceKind(ManyTestResourceKind *kind, char *name,
97 : : ResourceReleasePhase phase, uint32 priority)
98 : : {
99 : 6 : kind->desc.name = name;
100 : 6 : kind->desc.release_phase = phase;
101 : 6 : kind->desc.release_priority = priority;
102 : 6 : kind->desc.ReleaseResource = ReleaseManyTestResource;
103 : 6 : kind->desc.DebugPrint = PrintManyTest;
104 : 6 : kind->nremembered = 0;
105 : 6 : kind->nforgotten = 0;
106 : 6 : kind->nreleased = 0;
107 : 6 : kind->nleaked = 0;
108 : 6 : dlist_init(&kind->current_resources);
109 : 6 : }
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 : 2 : RememberManyTestResources(ResourceOwner owner,
117 : : ManyTestResourceKind *kinds, int nkinds,
118 : : int nresources)
119 : : {
120 : 2 : int kind_idx = 0;
121 : :
122 [ + + ]: 200002 : for (int i = 0; i < nresources; i++)
123 : : {
124 : 200000 : ManyTestResource *mres = palloc(sizeof(ManyTestResource));
125 : :
126 : 200000 : mres->kind = &kinds[kind_idx];
127 : 200000 : dlist_node_init(&mres->node);
128 : :
129 : 200000 : ResourceOwnerEnlarge(owner);
130 : 200000 : ResourceOwnerRemember(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
131 : 200000 : kinds[kind_idx].nremembered++;
132 : 200000 : dlist_push_tail(&kinds[kind_idx].current_resources, &mres->node);
133 : :
134 [ - + ]: 200000 : elog(DEBUG1, "remembered resource %p from %s", mres, mres->kind->desc.name);
135 : :
136 : 200000 : kind_idx = (kind_idx + 1) % nkinds;
137 : : }
138 : 2 : }
139 : :
140 : : /*
141 : : * Forget 'nresources' resources, in round robin fashion from 'kinds'.
142 : : */
143 : : static void
144 : 2 : ForgetManyTestResources(ResourceOwner owner,
145 : : ManyTestResourceKind *kinds, int nkinds,
146 : : int nresources)
147 : : {
148 : 2 : int kind_idx = 0;
149 : : int ntotal;
150 : :
151 : 2 : ntotal = GetTotalResourceCount(kinds, nkinds);
152 [ - + ]: 2 : if (ntotal < nresources)
668 heikki.linnakangas@i 153 [ # # ]:UBC 0 : elog(PANIC, "cannot free %d resources, only %d remembered", nresources, ntotal);
154 : :
668 heikki.linnakangas@i 155 [ + + ]:CBC 1002 : for (int i = 0; i < nresources; i++)
156 : : {
157 : 1000 : bool found = false;
158 : :
159 [ + - ]: 1000 : for (int j = 0; j < nkinds; j++)
160 : : {
161 : 1000 : kind_idx = (kind_idx + 1) % nkinds;
162 [ + - ]: 1000 : if (!dlist_is_empty(&kinds[kind_idx].current_resources))
163 : : {
164 : 1000 : ManyTestResource *mres = dlist_head_element(ManyTestResource, node, &kinds[kind_idx].current_resources);
165 : :
166 : 1000 : ResourceOwnerForget(owner, PointerGetDatum(mres), &kinds[kind_idx].desc);
167 : 1000 : kinds[kind_idx].nforgotten++;
168 : 1000 : dlist_delete(&mres->node);
169 : 1000 : pfree(mres);
170 : :
171 : 1000 : found = true;
172 : 1000 : break;
173 : : }
174 : : }
175 [ - + ]: 1000 : if (!found)
668 heikki.linnakangas@i 176 [ # # ]:UBC 0 : elog(ERROR, "could not find a test resource to forget");
177 : : }
668 heikki.linnakangas@i 178 :CBC 2 : }
179 : :
180 : : /*
181 : : * Get total number of currently active resources among 'kinds'.
182 : : */
183 : : static int
184 : 5 : GetTotalResourceCount(ManyTestResourceKind *kinds, int nkinds)
185 : : {
186 : 5 : int ntotal = 0;
187 : :
188 [ + + ]: 20 : for (int i = 0; i < nkinds; i++)
189 : 15 : ntotal += kinds[i].nremembered - kinds[i].nforgotten - kinds[i].nreleased;
190 : :
191 : 5 : 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 : 2 : PG_FUNCTION_INFO_V1(test_resowner_many);
202 : : Datum
203 : 1 : test_resowner_many(PG_FUNCTION_ARGS)
204 : : {
205 : 1 : int32 nkinds = PG_GETARG_INT32(0);
206 : 1 : int32 nremember_bl = PG_GETARG_INT32(1);
207 : 1 : int32 nforget_bl = PG_GETARG_INT32(2);
208 : 1 : int32 nremember_al = PG_GETARG_INT32(3);
209 : 1 : 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 [ - + ]: 1 : if (nkinds < 0)
668 heikki.linnakangas@i 218 [ # # ]:UBC 0 : elog(ERROR, "nkinds must be >= 0");
668 heikki.linnakangas@i 219 [ - + ]:CBC 1 : if (nremember_bl < 0)
668 heikki.linnakangas@i 220 [ # # ]:UBC 0 : elog(ERROR, "nremember_bl must be >= 0");
668 heikki.linnakangas@i 221 [ + - - + ]:CBC 1 : if (nforget_bl < 0 || nforget_bl > nremember_bl)
668 heikki.linnakangas@i 222 [ # # ]:UBC 0 : elog(ERROR, "nforget_bl must between 0 and 'nremember_bl'");
668 heikki.linnakangas@i 223 [ - + ]:CBC 1 : if (nremember_al < 0)
668 heikki.linnakangas@i 224 [ # # ]:UBC 0 : elog(ERROR, "nremember_al must be greater than zero");
668 heikki.linnakangas@i 225 [ + - - + ]:CBC 1 : if (nforget_al < 0 || nforget_al > nremember_al)
668 heikki.linnakangas@i 226 [ # # ]:UBC 0 : elog(ERROR, "nforget_al must between 0 and 'nremember_al'");
227 : :
228 : : /* Initialize all the different resource kinds to use */
668 heikki.linnakangas@i 229 :CBC 1 : before_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
230 [ + + ]: 4 : for (int i = 0; i < nkinds; i++)
231 : : {
232 : 3 : InitManyTestResourceKind(&before_kinds[i],
233 : : psprintf("resource before locks %d", i),
234 : : RESOURCE_RELEASE_BEFORE_LOCKS,
235 : 3 : RELEASE_PRIO_FIRST + i);
236 : : }
237 : 1 : after_kinds = palloc(nkinds * sizeof(ManyTestResourceKind));
238 [ + + ]: 4 : for (int i = 0; i < nkinds; i++)
239 : : {
240 : 3 : InitManyTestResourceKind(&after_kinds[i],
241 : : psprintf("resource after locks %d", i),
242 : : RESOURCE_RELEASE_AFTER_LOCKS,
243 : 3 : RELEASE_PRIO_FIRST + i);
244 : : }
245 : :
246 : 1 : resowner = ResourceOwnerCreate(CurrentResourceOwner, "TestOwner");
247 : :
248 : : /* Remember a bunch of resources */
249 [ + - ]: 1 : if (nremember_bl > 0)
250 : : {
251 [ + - ]: 1 : elog(NOTICE, "remembering %d before-locks resources", nremember_bl);
252 : 1 : RememberManyTestResources(resowner, before_kinds, nkinds, nremember_bl);
253 : : }
254 [ + - ]: 1 : if (nremember_al > 0)
255 : : {
256 [ + - ]: 1 : elog(NOTICE, "remembering %d after-locks resources", nremember_al);
257 : 1 : RememberManyTestResources(resowner, after_kinds, nkinds, nremember_al);
258 : : }
259 : :
260 : : /* Forget what was remembered */
261 [ + - ]: 1 : if (nforget_bl > 0)
262 : : {
263 [ + - ]: 1 : elog(NOTICE, "forgetting %d before-locks resources", nforget_bl);
264 : 1 : ForgetManyTestResources(resowner, before_kinds, nkinds, nforget_bl);
265 : : }
266 : :
267 [ + - ]: 1 : if (nforget_al > 0)
268 : : {
269 [ + - ]: 1 : elog(NOTICE, "forgetting %d after-locks resources", nforget_al);
270 : 1 : ForgetManyTestResources(resowner, after_kinds, nkinds, nforget_al);
271 : : }
272 : :
273 : : /* Start releasing */
274 [ + - ]: 1 : elog(NOTICE, "releasing resources before locks");
275 : 1 : current_release_phase = RESOURCE_RELEASE_BEFORE_LOCKS;
276 : 1 : last_release_priority = 0;
277 : 1 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_BEFORE_LOCKS, false, false);
278 [ - + ]: 1 : Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
279 : :
280 [ + - ]: 1 : elog(NOTICE, "releasing locks");
281 : 1 : current_release_phase = RESOURCE_RELEASE_LOCKS;
282 : 1 : last_release_priority = 0;
283 : 1 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_LOCKS, false, false);
284 : :
285 [ + - ]: 1 : elog(NOTICE, "releasing resources after locks");
286 : 1 : current_release_phase = RESOURCE_RELEASE_AFTER_LOCKS;
287 : 1 : last_release_priority = 0;
288 : 1 : ResourceOwnerRelease(resowner, RESOURCE_RELEASE_AFTER_LOCKS, false, false);
289 [ - + ]: 1 : Assert(GetTotalResourceCount(before_kinds, nkinds) == 0);
290 [ - + ]: 1 : Assert(GetTotalResourceCount(after_kinds, nkinds) == 0);
291 : :
292 : 1 : ResourceOwnerDelete(resowner);
293 : :
294 : 1 : PG_RETURN_VOID();
295 : : }
|