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