mirror of https://gerrit.osmocom.org/libosmocore
477 lines
11 KiB
C
477 lines
11 KiB
C
|
/* Scenarios of parent/child FSM instances cleaning up and deallocating from various triggers. */
|
||
|
|
||
|
#include <talloc.h>
|
||
|
|
||
|
#include <osmocom/core/application.h>
|
||
|
#include <osmocom/core/logging.h>
|
||
|
#include <osmocom/core/fsm.h>
|
||
|
#include <osmocom/core/use_count.h>
|
||
|
|
||
|
enum event {
|
||
|
EV_DESTROY,
|
||
|
EV_CHILD_GONE,
|
||
|
EV_OTHER_GONE,
|
||
|
};
|
||
|
|
||
|
static const struct value_string test_fsm_event_names[] = {
|
||
|
OSMO_VALUE_STRING(EV_DESTROY),
|
||
|
OSMO_VALUE_STRING(EV_CHILD_GONE),
|
||
|
OSMO_VALUE_STRING(EV_OTHER_GONE),
|
||
|
{}
|
||
|
};
|
||
|
|
||
|
enum state {
|
||
|
ST_ALIVE,
|
||
|
ST_DESTROYING,
|
||
|
};
|
||
|
|
||
|
enum objname {
|
||
|
root = 0,
|
||
|
branch0,
|
||
|
twig0a,
|
||
|
twig0b,
|
||
|
branch1,
|
||
|
twig1a,
|
||
|
twig1b,
|
||
|
|
||
|
other,
|
||
|
scene_size
|
||
|
};
|
||
|
|
||
|
struct scene {
|
||
|
struct obj *o[scene_size];
|
||
|
|
||
|
/* The use count is actually just to help tracking what functions have not exited yet */
|
||
|
struct osmo_use_count use_count;
|
||
|
};
|
||
|
|
||
|
int use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line)
|
||
|
{
|
||
|
char buf[128];
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "%s\n", osmo_use_count_name_buf(buf, sizeof(buf), use_count_entry->use_count));
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* References to related actual objects that are tied to FSM instances. */
|
||
|
struct obj {
|
||
|
struct osmo_fsm_inst *fi;
|
||
|
struct scene *s;
|
||
|
struct obj *parent;
|
||
|
struct obj *child[2];
|
||
|
struct obj *other[3];
|
||
|
};
|
||
|
|
||
|
static void scene_forget_obj(struct scene *s, struct obj *obj)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < ARRAY_SIZE(obj->s->o); i++) {
|
||
|
if (obj->s->o[i] != obj)
|
||
|
continue;
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "scene forgets %s\n", obj->fi->id);
|
||
|
obj->s->o[i] = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct scene *g_scene = NULL;
|
||
|
|
||
|
#define GET() \
|
||
|
char *token = talloc_asprintf(g_scene, "%s.%s()", obj->fi->id, __func__); \
|
||
|
osmo_use_count_get_put(&g_scene->use_count, token, 1)
|
||
|
|
||
|
#define PUT() osmo_use_count_get_put(&g_scene->use_count, token, -1)
|
||
|
|
||
|
void alive_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||
|
{
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
||
|
}
|
||
|
|
||
|
/* Remove obj->other[*] reference, return true if found and removed, false if not. */
|
||
|
bool other_gone(struct obj *obj, struct obj *other)
|
||
|
{
|
||
|
int i;
|
||
|
GET();
|
||
|
for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
|
||
|
if (obj->other[i] == other) {
|
||
|
obj->other[i] = NULL;
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_OTHER_GONE: Dropped reference %s.other[%d] = %s\n", obj->fi->id, i,
|
||
|
other->fi->id);
|
||
|
PUT();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
PUT();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Remove obj->child[*] reference, return true if more children remain after this, false if all are gone */
|
||
|
bool child_gone(struct obj *obj, struct obj *child)
|
||
|
{
|
||
|
int i;
|
||
|
bool found;
|
||
|
if (!child) {
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.\n");
|
||
|
return true;
|
||
|
}
|
||
|
GET();
|
||
|
found = false;
|
||
|
for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
|
||
|
if (obj->child[i] == child) {
|
||
|
obj->child[i] = NULL;
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "EV_CHILD_GONE: Dropped reference %s.child[%d] = %s\n", obj->fi->id, i,
|
||
|
child->fi->id);
|
||
|
found = true;
|
||
|
}
|
||
|
}
|
||
|
if (!found)
|
||
|
LOGPFSML(obj->fi, LOGL_ERROR, "EV_CHILD_GONE: cannot find child %s\n",
|
||
|
child && child->fi ? child->fi->id : "(null)");
|
||
|
|
||
|
/* Any children left? */
|
||
|
for (i = 0; i < ARRAY_SIZE(obj->child); i++) {
|
||
|
if (obj->child[i]) {
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "still exists: child[%d]\n", i);
|
||
|
PUT();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
LOGPFSML(obj->fi, LOGL_DEBUG, "No more children\n");
|
||
|
PUT();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void alive(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||
|
{
|
||
|
struct obj *obj = fi->priv;
|
||
|
GET();
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_fsm_event_name(fi->fsm, event));
|
||
|
switch (event) {
|
||
|
case EV_OTHER_GONE:
|
||
|
if (other_gone(obj, data)) {
|
||
|
/* Something this object depends on is gone, trigger deallocation */
|
||
|
osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case EV_CHILD_GONE:
|
||
|
if (!child_gone(obj, data)) {
|
||
|
/* All children are gone. Deallocate. */
|
||
|
osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case EV_DESTROY:
|
||
|
osmo_fsm_inst_state_chg(fi, ST_DESTROYING, 0, 0);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
OSMO_ASSERT(false);
|
||
|
}
|
||
|
PUT();
|
||
|
}
|
||
|
|
||
|
void destroying_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
|
||
|
{
|
||
|
struct obj *obj = fi->priv;
|
||
|
GET();
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s() from %s\n", __func__, osmo_fsm_state_name(fi->fsm, prev_state));
|
||
|
osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, 0);
|
||
|
PUT();
|
||
|
}
|
||
|
|
||
|
void destroying(struct osmo_fsm_inst *fi, uint32_t event, void *data)
|
||
|
{
|
||
|
struct obj *obj = fi->priv;
|
||
|
GET();
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s(%s)\n", __func__, osmo_fsm_event_name(fi->fsm, event));
|
||
|
switch (event) {
|
||
|
case EV_OTHER_GONE:
|
||
|
other_gone(obj, data);
|
||
|
break;
|
||
|
|
||
|
case EV_CHILD_GONE:
|
||
|
child_gone(obj, data);
|
||
|
break;
|
||
|
|
||
|
case EV_DESTROY:
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "already destroying\n");
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
OSMO_ASSERT(false);
|
||
|
}
|
||
|
PUT();
|
||
|
}
|
||
|
|
||
|
#define S(x) (1 << (x))
|
||
|
|
||
|
static const struct osmo_fsm_state test_fsm_states[] = {
|
||
|
[ST_ALIVE] = {
|
||
|
.name = "alive",
|
||
|
.in_event_mask = 0
|
||
|
| S(EV_CHILD_GONE)
|
||
|
| S(EV_OTHER_GONE)
|
||
|
| S(EV_DESTROY)
|
||
|
,
|
||
|
.out_state_mask = 0
|
||
|
| S(ST_ALIVE)
|
||
|
| S(ST_DESTROYING)
|
||
|
,
|
||
|
.onenter = alive_onenter,
|
||
|
.action = alive,
|
||
|
},
|
||
|
[ST_DESTROYING] = {
|
||
|
.name = "destroying",
|
||
|
.in_event_mask = 0
|
||
|
| S(EV_CHILD_GONE)
|
||
|
| S(EV_OTHER_GONE)
|
||
|
| S(EV_DESTROY)
|
||
|
,
|
||
|
.out_state_mask = 0
|
||
|
| S(ST_DESTROYING)
|
||
|
,
|
||
|
.onenter = destroying_onenter,
|
||
|
.action = destroying,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
void cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||
|
{
|
||
|
struct obj *obj = fi->priv;
|
||
|
int i;
|
||
|
GET();
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
||
|
|
||
|
/* Remove from the scene overview for this test */
|
||
|
scene_forget_obj(obj->s, obj);
|
||
|
|
||
|
/* Signal "other" objects */
|
||
|
for (i = 0; i < ARRAY_SIZE(obj->other); i++) {
|
||
|
struct obj *other = obj->other[i];
|
||
|
if (!other)
|
||
|
continue;
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "removing reference %s.other[%d] -> %s\n",
|
||
|
obj->fi->id, i, other->fi->id);
|
||
|
obj->other[i] = NULL;
|
||
|
osmo_fsm_inst_dispatch(other->fi, EV_OTHER_GONE, obj);
|
||
|
}
|
||
|
|
||
|
if (obj->parent)
|
||
|
osmo_fsm_inst_dispatch(obj->parent->fi, EV_CHILD_GONE, obj);
|
||
|
|
||
|
/* children are handled by fsm.c: term event / osmo_fsm_inst_term_children() */
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s() done\n", __func__);
|
||
|
PUT();
|
||
|
}
|
||
|
|
||
|
int timer_cb(struct osmo_fsm_inst *fi)
|
||
|
{
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
void pre_term(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
|
||
|
{
|
||
|
LOGPFSML(fi, LOGL_DEBUG, "%s()\n", __func__);
|
||
|
}
|
||
|
|
||
|
struct osmo_fsm test_fsm = {
|
||
|
.name = "test",
|
||
|
.states = test_fsm_states,
|
||
|
.num_states = ARRAY_SIZE(test_fsm_states),
|
||
|
.cleanup = cleanup,
|
||
|
.timer_cb = timer_cb,
|
||
|
.event_names = test_fsm_event_names,
|
||
|
.pre_term = pre_term,
|
||
|
.log_subsys = DLGLOBAL,
|
||
|
};
|
||
|
|
||
|
void *ctx = NULL;
|
||
|
|
||
|
static struct obj *obj_alloc(struct scene *s, struct obj *parent, const char *id) {
|
||
|
struct osmo_fsm_inst *fi;
|
||
|
struct obj *obj;
|
||
|
if (!parent) {
|
||
|
fi = osmo_fsm_inst_alloc(&test_fsm, s, NULL, LOGL_DEBUG, id);
|
||
|
OSMO_ASSERT(fi);
|
||
|
} else {
|
||
|
fi = osmo_fsm_inst_alloc_child(&test_fsm, parent->fi, EV_CHILD_GONE);
|
||
|
OSMO_ASSERT(fi);
|
||
|
osmo_fsm_inst_update_id(fi, id);
|
||
|
}
|
||
|
|
||
|
obj = talloc_zero(fi, struct obj);
|
||
|
fi->priv = obj;
|
||
|
*obj = (struct obj){
|
||
|
.fi = fi,
|
||
|
.s = s,
|
||
|
.parent = parent,
|
||
|
};
|
||
|
|
||
|
if (parent) {
|
||
|
int i;
|
||
|
for (i = 0; i < ARRAY_SIZE(parent->child); i++) {
|
||
|
if (parent->child[i])
|
||
|
continue;
|
||
|
parent->child[i] = obj;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return obj;
|
||
|
};
|
||
|
|
||
|
void obj_add_other(struct obj *a, struct obj *b)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < ARRAY_SIZE(a->other); i++) {
|
||
|
if (a->other[i])
|
||
|
i++;
|
||
|
a->other[i] = b;
|
||
|
LOGPFSML(a->fi, LOGL_DEBUG, "%s.other[%d] = %s\n", a->fi->id, i, b->fi->id);
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void obj_set_other(struct obj *a, struct obj *b)
|
||
|
{
|
||
|
obj_add_other(a, b);
|
||
|
obj_add_other(b, a);
|
||
|
}
|
||
|
|
||
|
static struct scene *scene_alloc()
|
||
|
{
|
||
|
struct scene *s = talloc_zero(ctx, struct scene);
|
||
|
s->use_count.talloc_object = s;
|
||
|
s->use_count.use_cb = use_cb;
|
||
|
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "%s()\n", __func__);
|
||
|
|
||
|
/*
|
||
|
s->o[root] = obj_alloc(s, NULL, "root");
|
||
|
*/
|
||
|
|
||
|
s->o[branch0] = obj_alloc(s, s->o[root], "_branch0");
|
||
|
|
||
|
s->o[twig0a] = obj_alloc(s, s->o[branch0], "__twig0a");
|
||
|
|
||
|
/*
|
||
|
s->o[twig0b] = obj_alloc(s, s->o[branch0], "__twig0b");
|
||
|
|
||
|
s->o[branch1] = obj_alloc(s, s->o[root], "_branch1");
|
||
|
s->o[twig1a] = obj_alloc(s, s->o[branch1], "__twig1a");
|
||
|
s->o[twig1b] = obj_alloc(s, s->o[branch1], "__twig1b");
|
||
|
*/
|
||
|
|
||
|
s->o[other] = obj_alloc(s, NULL, "other");
|
||
|
|
||
|
obj_set_other(s->o[branch0], s->o[other]);
|
||
|
obj_set_other(s->o[twig0a], s->o[other]);
|
||
|
|
||
|
return s;
|
||
|
}
|
||
|
|
||
|
static int scene_dump(struct scene *s)
|
||
|
{
|
||
|
int i;
|
||
|
int got = 0;
|
||
|
for (i = 0; i < ARRAY_SIZE(s->o); i++) {
|
||
|
if (!s->o[i])
|
||
|
continue;
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, " %s\n", s->o[i]->fi->id);
|
||
|
got++;
|
||
|
}
|
||
|
return got;
|
||
|
}
|
||
|
|
||
|
static void scene_clean(struct scene *s)
|
||
|
{
|
||
|
int i;
|
||
|
for (i = 0; i < ARRAY_SIZE(s->o); i++) {
|
||
|
if (!s->o[i])
|
||
|
continue;
|
||
|
osmo_fsm_inst_term(s->o[i]->fi, OSMO_FSM_TERM_ERROR, 0);
|
||
|
s->o[i] = NULL;
|
||
|
}
|
||
|
talloc_free(s);
|
||
|
}
|
||
|
|
||
|
void obj_destroy(struct obj *obj)
|
||
|
{
|
||
|
osmo_fsm_inst_dispatch(obj->fi, EV_DESTROY, NULL);
|
||
|
}
|
||
|
|
||
|
void obj_term(struct obj *obj)
|
||
|
{
|
||
|
osmo_fsm_inst_term(obj->fi, OSMO_FSM_TERM_REGULAR, NULL);
|
||
|
}
|
||
|
|
||
|
void test_dealloc(enum objname trigger, bool by_destroy_event)
|
||
|
{
|
||
|
struct scene *s = scene_alloc();
|
||
|
const char *label = by_destroy_event ? "destroy-event" : "term";
|
||
|
int remain;
|
||
|
g_scene = s;
|
||
|
if (!s->o[trigger]) {
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- Test disabled: object %d was not created. Cleaning up.\n",
|
||
|
trigger);
|
||
|
scene_clean(s);
|
||
|
return;
|
||
|
}
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "------ before %s cascade, got:\n", label);
|
||
|
scene_dump(s);
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "---\n");
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- %s at %s\n", label, s->o[trigger]->fi->id);
|
||
|
|
||
|
if (by_destroy_event)
|
||
|
obj_destroy(s->o[trigger]);
|
||
|
else
|
||
|
obj_term(s->o[trigger]);
|
||
|
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- after %s cascade:\n", label);
|
||
|
remain = scene_dump(s);
|
||
|
if (remain) {
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- %d objects remain. cleaning up\n", remain);
|
||
|
} else
|
||
|
LOGP(DLGLOBAL, LOGL_DEBUG, "--- all deallocated.\n");
|
||
|
scene_clean(s);
|
||
|
}
|
||
|
|
||
|
int main(void)
|
||
|
{
|
||
|
enum objname trigger;
|
||
|
size_t ctx_blocks;
|
||
|
size_t ctx_size;
|
||
|
int by_destroy_event;
|
||
|
|
||
|
ctx = talloc_named_const(NULL, 0, "main");
|
||
|
osmo_init_logging2(ctx, NULL);
|
||
|
|
||
|
log_set_print_filename(osmo_stderr_target, 0);
|
||
|
log_set_print_level(osmo_stderr_target, 1);
|
||
|
log_set_print_category(osmo_stderr_target, 1);
|
||
|
log_set_print_category_hex(osmo_stderr_target, 0);
|
||
|
log_set_use_color(osmo_stderr_target, 0);
|
||
|
osmo_fsm_log_addr(false);
|
||
|
|
||
|
log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
|
||
|
|
||
|
osmo_fsm_register(&test_fsm);
|
||
|
|
||
|
ctx_blocks = talloc_total_blocks(ctx);
|
||
|
ctx_size = talloc_total_size(ctx);
|
||
|
|
||
|
for (trigger = 0; trigger < scene_size; trigger++) {
|
||
|
for (by_destroy_event = 0; by_destroy_event < 2; by_destroy_event++) {
|
||
|
test_dealloc(trigger, (bool)by_destroy_event);
|
||
|
if (ctx_blocks != talloc_total_blocks(ctx)
|
||
|
|| ctx_size != talloc_total_size(ctx)) {
|
||
|
talloc_report_full(ctx, stderr);
|
||
|
OSMO_ASSERT(false);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
talloc_free(ctx);
|
||
|
return 0;
|
||
|
}
|