libosmocore/tests/fsm/fsm_dealloc_test.err

3484 lines
258 KiB
Plaintext
Raw Normal View History

DLGLOBAL DEBUG scene_alloc()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at root
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),root.destroying(),root.other_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),root.destroying())
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 2 (root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 7 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 7 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 11 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 11 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 10 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 9 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 8 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 7 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.
DLGLOBAL DEBUG 6 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 4 (root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (root.alive(),root.destroying_onenter(),root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG 0 (-)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 1 (__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig0b.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 1 (__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive())
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig0a.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){alive}: No more children
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 3 (__twig0a.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Ignoring trigger to terminate: already terminating
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){destroying}: removing reference _branch0.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[0] = _branch0
DLGLOBAL DEBUG 2 (_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[1] -> _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.other_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(_branch1){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 10 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 2 (_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch0.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch0.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch0.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch0.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch0){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.destroying_onenter())
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){destroying}: removing reference _branch0.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[0] = _branch0
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[1] -> _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(_branch1){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: still exists: child[0]
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch0))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch0))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 12 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){destroying}: No more children
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: still exists: child[0]
DLGLOBAL DEBUG 10 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 9 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.destroying())
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.destroying_onenter())
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.destroying_onenter(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),other.alive())
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig0a.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){alive}: still exists: child[1]
DLGLOBAL DEBUG 2 (__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 1 (__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (__twig0a.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(__twig0a){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (__twig0a.alive(),__twig0a.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(__twig0a){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig0a){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0a){destroying}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){destroying}: cleanup()
DLGLOBAL DEBUG test(__twig0a){destroying}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){destroying}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 4 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){alive}: still exists: child[1]
DLGLOBAL DEBUG 4 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (__twig0a.alive(),__twig0a.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.destroying_onenter(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (__twig0a.alive(),__twig0a.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){destroying}: Deallocated
DLGLOBAL DEBUG 1 (__twig0a.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at __twig0b
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 1 (__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig0b.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 1 (__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
fsm: support graceful osmo_fsm_inst_term() cascades Add global flag osmo_fsm_term_safely() -- if set to true, enable the following behavior: Detect osmo_fsm_inst_term() occuring within osmo_fsm_inst_term(): - collect deallocations until the outermost osmo_fsm_inst_term() is done. - call osmo_fsm_inst_free() *after* dispatching the parent event. If a struct osmo_fsm_inst enters osmo_fsm_inst_term() while another is already within osmo_fsm_inst_term(), do not directly deallocate it, but talloc-reparent it to a separate talloc context, to be deallocated with the outermost FSM inst. The effect is that all osmo_fsm_inst freed within an osmo_fsm_inst_term() cascade will stay allocated until all osmo_fsm_inst_term() are complete and all of them will be deallocated at the same time. Mark the deferred deallocation state as __thread in an attempt to make cascaded deallocation handling threadsafe. Keep the enable/disable flag separate, so that it is global and not per-thread. The feature is showcased by fsm_dealloc_test.c: with this feature, all of those wild deallocation scenarios succeed. Make fsm_dealloc_test a normal regression test in testsuite.at. Rationale: It is difficult to gracefully handle deallocations of groups of FSM instances that reference each other. As soon as one child dispatching a cleanup event causes its parent to deallocate before fsm.c was ready for it, deallocation will hit a use-after-free. Before this patch, by using parent_term events and distinct "terminating" FSM states, parent/child FSMs can be taught to wait for all children to deallocate before deallocating the parent. But as soon as a non-child / non-parent FSM instance is involved, or actually any other cleanup() action that triggers parent FSMs or parent talloc contexts to become unused, it is near impossible to think of all possible deallocation events ricocheting, and to avoid running into freeing FSM instances that were still in the middle of osmo_fsm_inst_term(), or FSM instances to enter osmo_fsm_inst_term() more than once. This patch makes deallocation of "all possible" setups of complex cross referencing FSM instances easy to handle correctly, without running into use-after-free or double free situations, and, notably, without changing calling code. Change-Id: I8eda67540a1cd444491beb7856b9fcd0a3143b18
2019-03-24 04:56:21 +00:00
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at __twig0b
DLGLOBAL DEBUG test(__twig0b){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (__twig0b.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(__twig0b){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (__twig0b.alive(),__twig0b.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(__twig0b){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig0b){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){destroying}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 3 (__twig0b.alive(),__twig0b.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){destroying}: cleanup()
DLGLOBAL DEBUG test(__twig0b){destroying}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (__twig0b.alive(),__twig0b.destroying_onenter(),__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (__twig0b.alive(),__twig0b.destroying_onenter(),__twig0b.cleanup(),_branch0.alive(),_branch0.child_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){alive}: still exists: child[0]
DLGLOBAL DEBUG 4 (__twig0b.alive(),__twig0b.destroying_onenter(),__twig0b.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 3 (__twig0b.alive(),__twig0b.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (__twig0b.alive(),__twig0b.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (__twig0b.alive(),__twig0b.destroying_onenter(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (__twig0b.alive(),__twig0b.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){destroying}: Deallocated
DLGLOBAL DEBUG 1 (__twig0b.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(_branch1){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: still exists: child[0]
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){destroying}: No more children
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(_branch1))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(_branch1))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 11 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 11 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 10 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 9 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_
DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.destroying_onenter(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 1 (_branch1.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){alive}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 6 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 12 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 6 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 12 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 12 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 12 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 11 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 10 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),root.destroying_onenter(),root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (__twig1a.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(__twig1a){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(__twig1a){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig1a){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1a){destroying}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){destroying}: cleanup()
DLGLOBAL DEBUG test(__twig1a){destroying}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){destroying}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup(),
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){alive}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(__twig1a))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 6 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 14 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 6 (cause = OSMO_FSM_TERM_PARENT, caused by: test(__twig1a))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 14 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 14 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 14 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 13 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 12 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 11 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup()
DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup(),
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.destroying_onenter(),_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 1 (__twig1a.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at __twig1b
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 2 (__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 1 (_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at __twig1b
DLGLOBAL DEBUG test(__twig1b){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (__twig1b.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(__twig1b){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (__twig1b.alive(),__twig1b.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(__twig1b){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(__twig1b){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){destroying}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (__twig1b.alive(),__twig1b.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){destroying}: cleanup()
DLGLOBAL DEBUG test(__twig1b){destroying}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (__twig1b.alive(),__twig1b.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 5 (__twig1b.alive(),__twig1b.destroying_onenter(),__twig1b.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){alive}: still exists: child[0]
DLGLOBAL DEBUG 4 (__twig1b.alive(),__twig1b.destroying_onenter(),__twig1b.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 3 (__twig1b.alive(),__twig1b.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1b.alive(),__twig1b.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 3 (__twig1b.alive(),__twig1b.destroying_onenter(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 2 (__twig1b.alive(),__twig1b.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){destroying}: Deallocated
DLGLOBAL DEBUG 1 (__twig1b.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG other
DLGLOBAL DEBUG --- 7 objects remain. cleaning up
DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
DLGLOBAL DEBUG test(root){alive}: pre_term()
DLGLOBAL DEBUG test(_branch1){alive}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.child_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){alive}: No more children
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(_branch1){destroying}: removing reference _branch1.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(root))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 5 (cause = OSMO_FSM_TERM_PARENT, caused by: test(root))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 9 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){destroying}: still exists: child[1]
DLGLOBAL DEBUG 8 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 1 (_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before term cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- term at other
DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(other){alive}: pre_term()
DLGLOBAL DEBUG 1 (other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup()
DLGLOBAL DEBUG test(other){alive}: scene forgets other
DLGLOBAL DEBUG test(other){alive}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying(),_branch0.child_gone(
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying(),_branch0.child_gone(
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_branch0.destroying())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.alive(),root.child_gone())
DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){alive}: still exists: child[1]
DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),root.alive())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 1 (other.cleanup())
DLGLOBAL DEBUG test(other){alive}: removing reference other.other[1] -> _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.other_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(_branch1){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying(),_branch1.child_gone(
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: still exists: child[0]
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.other_gone())
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 7 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter(),roo
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive(),root.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying(),_branch1.child_gone(
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){destroying}: No more children
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_branch1.destroying())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 6 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying(),root.child_gone())
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),root.destroying())
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 1 (other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(other){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(_branch0){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: is child of test(_branch0)
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: is child of test(root)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(_branch1){alive}: Allocated
DLGLOBAL DEBUG test(_branch1){alive}: is child of test(_branch1)
DLGLOBAL DEBUG test(other){alive}: Allocated
DLGLOBAL DEBUG test(_branch0){alive}: _branch0.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[0] = _branch0
DLGLOBAL DEBUG test(__twig0a){alive}: __twig0a.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = __twig0a
DLGLOBAL DEBUG test(_branch1){alive}: _branch1.other[0] = other
DLGLOBAL DEBUG test(other){alive}: other.other[1] = _branch1
DLGLOBAL DEBUG test(__twig1a){alive}: __twig1a.other[0] = root
DLGLOBAL DEBUG test(root){alive}: root.other[0] = __twig1a
DLGLOBAL DEBUG ------ before destroy-event cascade, got:
DLGLOBAL DEBUG root
DLGLOBAL DEBUG _branch0
DLGLOBAL DEBUG __twig0a
DLGLOBAL DEBUG __twig0b
DLGLOBAL DEBUG _branch1
DLGLOBAL DEBUG __twig1a
DLGLOBAL DEBUG __twig1b
DLGLOBAL DEBUG other
DLGLOBAL DEBUG ---
DLGLOBAL DEBUG --- destroy-event at other
DLGLOBAL DEBUG test(other){alive}: Received Event EV_DESTROY
DLGLOBAL DEBUG 1 (other.alive())
DLGLOBAL DEBUG test(other){alive}: alive(EV_DESTROY)
DLGLOBAL DEBUG test(other){alive}: state_chg to destroying
DLGLOBAL DEBUG 2 (other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(other){destroying}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
DLGLOBAL DEBUG test(other){destroying}: pre_term()
DLGLOBAL DEBUG 3 (other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup()
DLGLOBAL DEBUG test(other){destroying}: scene forgets other
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[0] -> _branch0
DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(_branch0){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch0){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(_branch0){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
DLGLOBAL DEBUG test(_branch0){destroying}: still exists: child[0]
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup(),_
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0b.cleanup())
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(__twig0a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig0a){alive}: scene forgets __twig0a
DLGLOBAL DEBUG test(__twig0a){alive}: removing reference __twig0a.other[0] -> other
DLGLOBAL DEBUG test(other){destroying}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),o
DLGLOBAL DEBUG test(other){destroying}: destroying(EV_OTHER_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),o
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),o
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_
DLGLOBAL DEBUG test(_branch0){destroying}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
DLGLOBAL DEBUG test(_branch0){destroying}: No more children
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup(),_
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),__twig0a.cleanup())
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(_branch0){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch0){destroying}: scene forgets _branch0
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),r
DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),r
DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[0] = _branch0
DLGLOBAL DEBUG test(root){alive}: still exists: child[1]
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup(),r
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive(),_branch0.destroying_onenter())
DLGLOBAL DEBUG test(_branch0){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG 3 (other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: removing reference other.other[1] -> _branch1
DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.other_gone())
DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(_branch1){alive}: state_chg to destroying
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(_branch1){destroying}: Terminating in cascade, depth 2 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(_branch1){destroying}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[1] = __twig1b
DLGLOBAL DEBUG test(_branch1){destroying}: still exists: child[0]
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup(),_
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1b.cleanup())
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(__twig1a){alive}: Terminating in cascade, depth 3 (cause = OSMO_FSM_TERM_PARENT, caused by: test(other))
DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup()
DLGLOBAL DEBUG test(__twig1a){alive}: scene forgets __twig1a
DLGLOBAL DEBUG test(__twig1a){alive}: removing reference __twig1a.other[0] -> root
DLGLOBAL DEBUG test(root){alive}: Received Event EV_OTHER_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){alive}: state_chg to destroying
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){destroying}: destroying_onenter() from alive
DLGLOBAL DEBUG test(root){destroying}: Terminating in cascade, depth 4 (cause = OSMO_FSM_TERM_REGULAR, caused by: test(other))
DLGLOBAL DEBUG test(root){destroying}: pre_term()
DLGLOBAL DEBUG test(_branch1){destroying}: Ignoring trigger to terminate: already terminating
DLGLOBAL ERROR test(root){destroying}: Internal error while terminating child FSMs: a child FSM is stuck
DLGLOBAL DEBUG 9 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){destroying}: cleanup()
DLGLOBAL DEBUG test(root){destroying}: scene forgets root
DLGLOBAL DEBUG test(root){destroying}: cleanup() done
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG test(root){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),r
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_
DLGLOBAL DEBUG test(_branch1){destroying}: EV_CHILD_GONE: Dropped reference _branch1.child[0] = __twig1a
DLGLOBAL DEBUG test(_branch1){destroying}: No more children
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup(),_
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),__twig1a.cleanup())
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG test(_branch1){destroying}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup()
DLGLOBAL DEBUG test(_branch1){destroying}: scene forgets _branch1
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),r
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG 8 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),r
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
DLGLOBAL DEBUG test(root){destroying}: No more children
DLGLOBAL DEBUG 7 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup(),r
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){destroying}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(root){destroying}: Received Event EV_CHILD_GONE
DLGLOBAL DEBUG 6 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter(),root.destroying())
DLGLOBAL DEBUG test(root){destroying}: destroying(EV_CHILD_GONE)
DLGLOBAL DEBUG test(root){destroying}: EV_CHILD_GONE with NULL data, must be a parent_term event. Ignore.
DLGLOBAL DEBUG 5 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive(),_branch1.destroying_onenter())
DLGLOBAL DEBUG test(_branch1){destroying}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 4 (other.alive(),other.destroying_onenter(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG 3 (other.alive(),other.destroying_onenter(),other.cleanup())
DLGLOBAL DEBUG test(other){destroying}: cleanup() done
DLGLOBAL DEBUG 2 (other.alive(),other.destroying_onenter())
DLGLOBAL DEBUG test(other){destroying}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 1 (other.alive())
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.