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