blob: 246ba563af282b09f0099b69d9a0b428b745f656 [file] [log] [blame]
Neels Hofmeyr223d66a2019-03-23 23:38:43 +01001/* Scenarios of parent/child FSM instances cleaning up and deallocating from various triggers. */
2
3#include <talloc.h>
4
5#include <osmocom/core/application.h>
6#include <osmocom/core/logging.h>
7#include <osmocom/core/fsm.h>
8#include <osmocom/core/use_count.h>
9
10enum event {
11 EV_DESTROY,
12 EV_CHILD_GONE,
13 EV_OTHER_GONE,
14};
15
16static const struct value_string test_fsm_event_names[] = {
17 OSMO_VALUE_STRING(EV_DESTROY),
18 OSMO_VALUE_STRING(EV_CHILD_GONE),
19 OSMO_VALUE_STRING(EV_OTHER_GONE),
20 {}
21};
22
23enum state {
24 ST_ALIVE,
Neels Hofmeyr223d66a2019-03-23 23:38:43 +010025};
26
27enum objname {
28 root = 0,
29 branch0,
30 twig0a,
31 twig0b,
32 branch1,
33 twig1a,
34 twig1b,
35
36 other,
37 scene_size
38};
39
40struct scene {
41 struct obj *o[scene_size];
42
43 /* The use count is actually just to help tracking what functions have not exited yet */
44 struct osmo_use_count use_count;
45};
46
47int use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line)
48{
49 char buf[128];
50 LOGP(DLGLOBAL, LOGL_DEBUG, "%s\n", osmo_use_count_name_buf(buf, sizeof(buf), use_count_entry->use_count));
51 return 0;
52}
53
54/* References to related actual objects that are tied to FSM instances. */
55struct obj {
56 struct osmo_fsm_inst *fi;
57 struct scene *s;
58 struct obj *parent;
59 struct obj *child[2];
60 struct obj *other[3];
61};
62
63static void scene_forget_obj(struct scene *s, struct obj *obj)
64{
65 int i;
66 for (i = 0; i < ARRAY_SIZE(obj->s->o); i++) {
67 if (obj->s->o[i] != obj)
68 continue;
69 LOGPFSML(obj->fi, LOGL_DEBUG, "scene forgets %s\n", obj->fi->id);
70 obj->s->o[i] = NULL;
71 }
72}
73
74struct scene *g_scene = NULL;
75
76#define GET() \
77 char *token = talloc_asprintf(g_scene, "%s.%s()", obj->fi->id, __func__); \
78 osmo_use_count_get_put(&g_scene->use_count, token, 1)
79
80#define PUT() osmo_use_count_get_put(&g_scene->use_count, token, -1)
81
82void alive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
83{
84 LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
85}
86
87/* Remove obj->other[*] reference, return true if found and removed, false if not. */
88bool other_gone(struct obj *obj, struct obj *other)
89{
90 int i;
91 GET();
92 for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
93 if (obj->other[i] == other) {
94 obj->other[i] = NULL;
95 LOGPFSML(obj->fi, LOGL_DEBUG, "EV_OTHER_GONE: Dropped reference %s.other[%d] = %s\n", obj->fi->id, i,
96 other->fi->id);
97 PUT();
98 return true;
99 }
100 }
101 PUT();
102 return false;
103}
104
105/* Remove obj->child[*] reference, return true if more children remain after this, false if all are gone */
106bool child_gone(struct obj *obj, struct obj *child)
107{
108 int i;
109 bool found;
110 if (!child) {
111 LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.\n");
112 return true;
113 }
114 GET();
115 found = false;
116 for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
117 if (obj->child[i] == child) {
118 obj->child[i] = NULL;
119 LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE: Dropped reference %s.child[%d] = %s\n", obj->fi->id, i,
120 child->fi->id);
121 found = true;
122 }
123 }
124 if (!found)
125 LOGPFSML(obj->fi, LOGL_ERROR, "EV_CHILD_GONE: cannot find child %s\n",
126 child && child->fi ? child->fi->id : "(null)");
127
128 /* Any children left? */
129 for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
130 if (obj->child[i]) {
131 LOGPFSML(obj->fi, LOGL_DEBUG, "still exists: child[%d]\n", i);
132 PUT();
133 return true;
134 }
135 }
136 LOGPFSML(obj->fi, LOGL_DEBUG, "No more children\n");
137 PUT();
138 return false;
139}
140
141void alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
142{
143 struct obj *obj = fi->priv;
144 GET();
145 LOGPFSML(fi, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_fsm_event_name(fi->fsm, event));
146 switch (event) {
147 case EV_OTHER_GONE:
148 if (other_gone(obj, data)) {
149 /* Something this object depends on is gone, trigger deallocation */
Neels Hofmeyrd28aa0c2019-03-24 05:43:04 +0100150 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100151 }
152 break;
153
154 case EV_CHILD_GONE:
155 if (!child_gone(obj, data)) {
156 /* All children are gone. Deallocate. */
Neels Hofmeyrd28aa0c2019-03-24 05:43:04 +0100157 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100158 }
159 break;
160
161 case EV_DESTROY:
Neels Hofmeyrd28aa0c2019-03-24 05:43:04 +0100162 osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100163 break;
164
165 default:
166 OSMO_ASSERT(false);
167 }
168 PUT();
169}
170
171#define S(x) (1 << (x))
172
173static const struct osmo_fsm_state test_fsm_states[] = {
174 [ST_ALIVE] = {
175 .name = "alive",
176 .in_event_mask = 0
177 | S(EV_CHILD_GONE)
178 | S(EV_OTHER_GONE)
179 | S(EV_DESTROY)
180 ,
181 .out_state_mask = 0
182 | S(ST_ALIVE)
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100183 ,
184 .onenter = alive_onenter,
185 .action = alive,
186 },
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100187};
188
189void cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
190{
191 struct obj *obj = fi->priv;
192 int i;
193 GET();
194 LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
195
196 /* Remove from the scene overview for this test */
197 scene_forget_obj(obj->s, obj);
198
199 /* Signal "other" objects */
200 for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
201 struct obj *other = obj->other[i];
202 if (!other)
203 continue;
204 LOGPFSML(fi, LOGL_DEBUG, "removing reference %s.other[%d] -> %s\n",
205 obj->fi->id, i, other->fi->id);
206 obj->other[i] = NULL;
207 osmo_fsm_inst_dispatch(other->fi, EV_OTHER_GONE, obj);
208 }
209
210 if (obj->parent)
211 osmo_fsm_inst_dispatch(obj->parent->fi, EV_CHILD_GONE, obj);
212
213 /* children are handled by fsm.c: term event / osmo_fsm_inst_term_children() */
214 LOGPFSML(fi, LOGL_DEBUG, "%s() done\n", __func__);
215 PUT();
216}
217
218int timer_cb(struct osmo_fsm_inst *fi)
219{
220 LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
221 return 1;
222}
223
224void pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
225{
226 LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
227}
228
229struct osmo_fsm test_fsm = {
230 .name = "test",
231 .states = test_fsm_states,
232 .num_states = ARRAY_SIZE(test_fsm_states),
233 .cleanup = cleanup,
234 .timer_cb = timer_cb,
235 .event_names = test_fsm_event_names,
236 .pre_term = pre_term,
237 .log_subsys = DLGLOBAL,
238};
239
240void *ctx = NULL;
241
242static struct obj *obj_alloc(struct scene *s, struct obj *parent, const char *id) {
243 struct osmo_fsm_inst *fi;
244 struct obj *obj;
245 if (!parent) {
246 fi = osmo_fsm_inst_alloc(&test_fsm, s, NULL, LOGL_DEBUG, id);
247 OSMO_ASSERT(fi);
248 } else {
249 fi = osmo_fsm_inst_alloc_child(&test_fsm, parent->fi, EV_CHILD_GONE);
250 OSMO_ASSERT(fi);
251 osmo_fsm_inst_update_id(fi, id);
252 }
253
254 obj = talloc_zero(fi, struct obj);
255 fi->priv = obj;
256 *obj = (struct obj){
257 .fi = fi,
258 .s = s,
259 .parent = parent,
260 };
261
262 if (parent) {
263 int i;
264 for (i = 0; i < ARRAY_SIZE(parent->child); i++) {
265 if (parent->child[i])
266 continue;
267 parent->child[i] = obj;
268 break;
269 }
270 }
271
272 return obj;
273};
274
275void obj_add_other(struct obj *a, struct obj *b)
276{
277 int i;
278 for (i = 0; i < ARRAY_SIZE(a->other); i++) {
279 if (a->other[i])
280 i++;
281 a->other[i] = b;
282 LOGPFSML(a->fi, LOGL_DEBUG, "%s.other[%d] = %s\n", a->fi->id, i, b->fi->id);
283 return;
284 }
285}
286
287void obj_set_other(struct obj *a, struct obj *b)
288{
289 obj_add_other(a, b);
290 obj_add_other(b, a);
291}
292
Harald Weltee61d4592022-11-03 11:05:58 +0100293static struct scene *scene_alloc(void)
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100294{
295 struct scene *s = talloc_zero(ctx, struct scene);
296 s->use_count.talloc_object = s;
297 s->use_count.use_cb = use_cb;
298
299 LOGP(DLGLOBAL, LOGL_DEBUG, "%s()\n", __func__);
300
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100301 s->o[root] = obj_alloc(s, NULL, "root");
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100302
303 s->o[branch0] = obj_alloc(s, s->o[root], "_branch0");
304
305 s->o[twig0a] = obj_alloc(s, s->o[branch0], "__twig0a");
306
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100307 s->o[twig0b] = obj_alloc(s, s->o[branch0], "__twig0b");
308
309 s->o[branch1] = obj_alloc(s, s->o[root], "_branch1");
310 s->o[twig1a] = obj_alloc(s, s->o[branch1], "__twig1a");
311 s->o[twig1b] = obj_alloc(s, s->o[branch1], "__twig1b");
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100312
313 s->o[other] = obj_alloc(s, NULL, "other");
314
315 obj_set_other(s->o[branch0], s->o[other]);
316 obj_set_other(s->o[twig0a], s->o[other]);
Neels Hofmeyr1f9cc012019-03-24 05:56:21 +0100317 obj_set_other(s->o[branch1], s->o[other]);
318 obj_set_other(s->o[twig1a], s->o[root]);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100319
320 return s;
321}
322
323static int scene_dump(struct scene *s)
324{
325 int i;
326 int got = 0;
327 for (i = 0; i < ARRAY_SIZE(s->o); i++) {
328 if (!s->o[i])
329 continue;
330 LOGP(DLGLOBAL, LOGL_DEBUG, " %s\n", s->o[i]->fi->id);
331 got++;
332 }
333 return got;
334}
335
336static void scene_clean(struct scene *s)
337{
338 int i;
339 for (i = 0; i < ARRAY_SIZE(s->o); i++) {
340 if (!s->o[i])
341 continue;
342 osmo_fsm_inst_term(s->o[i]->fi, OSMO_FSM_TERM_ERROR, 0);
343 s->o[i] = NULL;
344 }
345 talloc_free(s);
346}
347
348void obj_destroy(struct obj *obj)
349{
350 osmo_fsm_inst_dispatch(obj->fi, EV_DESTROY, NULL);
351}
352
353void obj_term(struct obj *obj)
354{
355 osmo_fsm_inst_term(obj->fi, OSMO_FSM_TERM_REGULAR, NULL);
356}
357
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200358void test_dealloc(enum objname trigger, bool by_destroy_event, void *loop_ctx)
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100359{
360 struct scene *s = scene_alloc();
361 const char *label = by_destroy_event ? "destroy-event" : "term";
362 int remain;
363 g_scene = s;
364 if (!s->o[trigger]) {
365 LOGP(DLGLOBAL, LOGL_DEBUG, "--- Test disabled: object %d was not created. Cleaning up.\n",
366 trigger);
367 scene_clean(s);
368 return;
369 }
370 LOGP(DLGLOBAL, LOGL_DEBUG, "------ before %s cascade, got:\n", label);
371 scene_dump(s);
372 LOGP(DLGLOBAL, LOGL_DEBUG, "---\n");
373 LOGP(DLGLOBAL, LOGL_DEBUG, "--- %s at %s\n", label, s->o[trigger]->fi->id);
374
375 if (by_destroy_event)
376 obj_destroy(s->o[trigger]);
377 else
378 obj_term(s->o[trigger]);
379
380 LOGP(DLGLOBAL, LOGL_DEBUG, "--- after %s cascade:\n", label);
381 remain = scene_dump(s);
382 if (remain) {
383 LOGP(DLGLOBAL, LOGL_DEBUG, "--- %d objects remain. cleaning up\n", remain);
384 } else
385 LOGP(DLGLOBAL, LOGL_DEBUG, "--- all deallocated.\n");
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200386
387 if (loop_ctx) {
388 fprintf(stderr, "*** loop_ctx contains %zu blocks, deallocating.\n",
389 talloc_total_blocks(loop_ctx));
390 talloc_free_children(loop_ctx);
391 }
392
393 /* Silently free the remaining objects. */
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100394 scene_clean(s);
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200395 if (loop_ctx)
396 talloc_free_children(loop_ctx);
397}
398
399static void trigger_tests(void *loop_ctx)
400{
401 size_t ctx_blocks;
402 size_t ctx_size;
403 enum objname trigger;
404 int by_destroy_event;
405
406 ctx_blocks = talloc_total_blocks(ctx);
407 ctx_size = talloc_total_size(ctx);
408
409 for (trigger = 0; trigger < scene_size; trigger++) {
410 for (by_destroy_event = 0; by_destroy_event < 2; by_destroy_event++) {
411 test_dealloc(trigger, (bool)by_destroy_event, loop_ctx);
412
413 if (ctx_blocks != talloc_total_blocks(ctx)
414 || ctx_size != talloc_total_size(ctx)) {
415 talloc_report_full(ctx, stderr);
416 OSMO_ASSERT(false);
417 }
418 }
419 }
420}
421
Harald Weltee61d4592022-11-03 11:05:58 +0100422void test_osmo_fsm_term_safely(void)
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200423{
424 fprintf(stderr, "\n\n%s()\n", __func__);
425 osmo_fsm_term_safely(true);
426 trigger_tests(NULL);
427 osmo_fsm_term_safely(false);
428 fprintf(stderr, "\n\n%s() done\n", __func__);
429}
430
Harald Weltee61d4592022-11-03 11:05:58 +0100431void test_osmo_fsm_set_dealloc_ctx(void)
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200432{
433 fprintf(stderr, "\n\n%s()\n", __func__);
434 void *dealloc_ctx = talloc_named_const(ctx, 0, "fsm_dealloc");
435 osmo_fsm_set_dealloc_ctx(dealloc_ctx);
436 trigger_tests(dealloc_ctx);
437 osmo_fsm_set_dealloc_ctx(NULL);
438 fprintf(stderr, "\n\n%s() done\n", __func__);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100439}
440
441int main(void)
442{
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100443 ctx = talloc_named_const(NULL, 0, "main");
444 osmo_init_logging2(ctx, NULL);
445
Pau Espin Pedrol01e0d3e2021-02-18 19:25:44 +0100446 log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100447 log_set_print_level(osmo_stderr_target, 1);
448 log_set_print_category(osmo_stderr_target, 1);
449 log_set_print_category_hex(osmo_stderr_target, 0);
450 log_set_use_color(osmo_stderr_target, 0);
451 osmo_fsm_log_addr(false);
452
453 log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
454
Harald Welteae5016f2019-12-01 13:38:20 +0100455 OSMO_ASSERT(osmo_fsm_register(&test_fsm) == 0);
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100456
Neels Hofmeyr988f6d72019-10-04 20:37:17 +0200457 test_osmo_fsm_term_safely();
458 test_osmo_fsm_set_dealloc_ctx();
Neels Hofmeyr223d66a2019-03-23 23:38:43 +0100459
460 talloc_free(ctx);
461 return 0;
462}